mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
fix code block
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import React, { type HTMLAttributes, memo, type ReactNode, useMemo } from 'react'
|
||||
import { code } from '@streamdown/code'
|
||||
import { Streamdown } from 'streamdown'
|
||||
import 'streamdown/styles.css'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
@@ -26,8 +25,6 @@ export function LinkWithPreview({ href, children }: { href: string; children: Re
|
||||
)
|
||||
}
|
||||
|
||||
const STREAMDOWN_PLUGINS = { code }
|
||||
|
||||
function createCustomComponents(LinkComponent: typeof LinkWithPreview) {
|
||||
return {
|
||||
p: ({ children }: React.HTMLAttributes<HTMLParagraphElement>) => (
|
||||
@@ -195,7 +192,7 @@ const MarkdownRenderer = memo(function MarkdownRenderer({
|
||||
|
||||
return (
|
||||
<div className='space-y-4 break-words font-sans text-[var(--landing-text)] text-base leading-relaxed'>
|
||||
<Streamdown mode='static' plugins={STREAMDOWN_PLUGINS} components={components}>
|
||||
<Streamdown mode='static' components={components}>
|
||||
{processedContent}
|
||||
</Streamdown>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef } from 'react'
|
||||
import { code } from '@streamdown/code'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
@@ -77,7 +76,6 @@ export const PreviewPanel = memo(function PreviewPanel({
|
||||
|
||||
const REMARK_PLUGINS = [remarkBreaks]
|
||||
const REHYPE_PLUGINS = [rehypeSlug]
|
||||
const STREAMDOWN_PLUGINS = { code }
|
||||
|
||||
/**
|
||||
* Carries the contentRef and toggle handler from MarkdownPreview down to the
|
||||
@@ -380,7 +378,6 @@ const MarkdownPreview = memo(function MarkdownPreview({
|
||||
mode='static'
|
||||
remarkPlugins={REMARK_PLUGINS}
|
||||
rehypePlugins={REHYPE_PLUGINS}
|
||||
plugins={STREAMDOWN_PLUGINS}
|
||||
components={MARKDOWN_COMPONENTS}
|
||||
>
|
||||
{content}
|
||||
@@ -398,7 +395,6 @@ const MarkdownPreview = memo(function MarkdownPreview({
|
||||
mode='static'
|
||||
remarkPlugins={REMARK_PLUGINS}
|
||||
rehypePlugins={REHYPE_PLUGINS}
|
||||
plugins={STREAMDOWN_PLUGINS}
|
||||
components={MARKDOWN_COMPONENTS}
|
||||
>
|
||||
{content}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { type ComponentPropsWithoutRef, useMemo } from 'react'
|
||||
import { code } from '@streamdown/code'
|
||||
import { Streamdown } from 'streamdown'
|
||||
import 'streamdown/styles.css'
|
||||
import { Checkbox } from '@/components/emcn'
|
||||
import 'prismjs/components/prism-typescript'
|
||||
import 'prismjs/components/prism-bash'
|
||||
import 'prismjs/components/prism-css'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import '@/components/emcn/components/code/code.css'
|
||||
import { Checkbox, highlight, languages } from '@/components/emcn'
|
||||
import { CopyCodeButton } from '@/components/ui/copy-code-button'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { extractTextContent } from '@/lib/core/utils/react-node-text'
|
||||
import {
|
||||
PendingTagIndicator,
|
||||
parseSpecialTags,
|
||||
@@ -13,6 +19,19 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags'
|
||||
import { useStreamingText } from '@/hooks/use-streaming-text'
|
||||
|
||||
const LANG_ALIASES: Record<string, string> = {
|
||||
js: 'javascript',
|
||||
ts: 'typescript',
|
||||
tsx: 'typescript',
|
||||
jsx: 'javascript',
|
||||
sh: 'bash',
|
||||
shell: 'bash',
|
||||
html: 'markup',
|
||||
xml: 'markup',
|
||||
yml: 'yaml',
|
||||
py: 'python',
|
||||
}
|
||||
|
||||
const PROSE_CLASSES = cn(
|
||||
'prose prose-base dark:prose-invert max-w-none',
|
||||
'font-[family-name:var(--font-inter)] antialiased break-words font-[430] tracking-[0]',
|
||||
@@ -24,8 +43,6 @@ const PROSE_CLASSES = cn(
|
||||
'prose-ul:my-4 prose-ol:my-4',
|
||||
'prose-strong:font-[600] prose-strong:text-[var(--text-primary)]',
|
||||
'prose-a:text-[var(--text-primary)] prose-a:underline prose-a:decoration-dashed prose-a:underline-offset-4',
|
||||
'prose-code:rounded prose-code:bg-[var(--surface-5)] prose-code:px-1.5 prose-code:py-0.5 prose-code:text-small prose-code:font-mono prose-code:font-[400] prose-code:text-[var(--text-primary)]',
|
||||
'prose-code:before:content-none prose-code:after:content-none',
|
||||
'prose-hr:border-[var(--divider)] prose-hr:my-6',
|
||||
'prose-table:my-0'
|
||||
)
|
||||
@@ -33,8 +50,6 @@ const PROSE_CLASSES = cn(
|
||||
type TdProps = ComponentPropsWithoutRef<'td'>
|
||||
type ThProps = ComponentPropsWithoutRef<'th'>
|
||||
|
||||
const STREAMDOWN_PLUGINS = { code }
|
||||
|
||||
const MARKDOWN_COMPONENTS = {
|
||||
table({ children }: { children?: React.ReactNode }) {
|
||||
return (
|
||||
@@ -68,6 +83,41 @@ const MARKDOWN_COMPONENTS = {
|
||||
</td>
|
||||
)
|
||||
},
|
||||
code({ children, className }: { children?: React.ReactNode; className?: string }) {
|
||||
const langMatch = className?.match(/language-(\w+)/)
|
||||
const language = langMatch ? langMatch[1] : ''
|
||||
const codeString = extractTextContent(children)
|
||||
|
||||
if (!codeString) {
|
||||
return (
|
||||
<pre className='not-prose my-6 overflow-x-auto rounded-lg bg-[var(--surface-5)] p-4 font-[430] font-mono text-[var(--text-primary)] text-small leading-[21px] dark:bg-[var(--code-bg)]'>
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
const resolved = LANG_ALIASES[language] || language || 'javascript'
|
||||
const grammar = languages[resolved] || languages.javascript
|
||||
const html = highlight(codeString.trimEnd(), grammar, resolved)
|
||||
|
||||
return (
|
||||
<div className='not-prose my-6 overflow-hidden rounded-lg border border-[var(--divider)]'>
|
||||
<div className='flex items-center justify-between border-[var(--divider)] border-b bg-[var(--surface-4)] px-4 py-2 dark:bg-[var(--surface-4)]'>
|
||||
<span className='text-[var(--text-tertiary)] text-xs'>{language || 'code'}</span>
|
||||
<CopyCodeButton
|
||||
code={codeString}
|
||||
className='text-[var(--text-tertiary)] hover:bg-[var(--surface-5)] hover:text-[var(--text-secondary)]'
|
||||
/>
|
||||
</div>
|
||||
<div className='code-editor-theme bg-[var(--surface-5)] dark:bg-[var(--code-bg)]'>
|
||||
<pre
|
||||
className='m-0 overflow-x-auto whitespace-pre p-4 font-[430] font-mono text-[var(--text-primary)] text-small leading-[21px]'
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
a({ children, href }: { children?: React.ReactNode; href?: string }) {
|
||||
return (
|
||||
<a
|
||||
@@ -103,6 +153,13 @@ const MARKDOWN_COMPONENTS = {
|
||||
</li>
|
||||
)
|
||||
},
|
||||
inlineCode({ children }: { children?: React.ReactNode }) {
|
||||
return (
|
||||
<code className='rounded bg-[var(--surface-5)] px-1.5 py-0.5 font-mono text-small font-[400] text-[var(--text-primary)] before:content-none after:content-none'>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
input({ type, checked }: { type?: string; checked?: boolean }) {
|
||||
if (type === 'checkbox') {
|
||||
return <Checkbox checked={checked || false} disabled size='sm' className='mt-1.5 shrink-0' />
|
||||
@@ -133,11 +190,7 @@ export function ChatContent({ content, isStreaming = false, onOptionSelect }: Ch
|
||||
key={`${segment.type}-${i}`}
|
||||
className={cn(PROSE_CLASSES, '[&>:first-child]:mt-0 [&>:last-child]:mb-0')}
|
||||
>
|
||||
<Streamdown
|
||||
mode='static'
|
||||
plugins={STREAMDOWN_PLUGINS}
|
||||
components={MARKDOWN_COMPONENTS}
|
||||
>
|
||||
<Streamdown mode='static' components={MARKDOWN_COMPONENTS}>
|
||||
{segment.content}
|
||||
</Streamdown>
|
||||
</div>
|
||||
@@ -154,12 +207,7 @@ export function ChatContent({ content, isStreaming = false, onOptionSelect }: Ch
|
||||
|
||||
return (
|
||||
<div className={cn(PROSE_CLASSES, '[&>:first-child]:mt-0 [&>:last-child]:mb-0')}>
|
||||
<Streamdown
|
||||
isAnimating={isStreaming}
|
||||
animated
|
||||
plugins={STREAMDOWN_PLUGINS}
|
||||
components={MARKDOWN_COMPONENTS}
|
||||
>
|
||||
<Streamdown isAnimating={isStreaming} animated components={MARKDOWN_COMPONENTS}>
|
||||
{rendered}
|
||||
</Streamdown>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +92,6 @@
|
||||
"@react-email/render": "2.0.0",
|
||||
"@sim/logger": "workspace:*",
|
||||
"@socket.io/redis-adapter": "8.3.0",
|
||||
"@streamdown/code": "1.1.1",
|
||||
"@t3-oss/env-nextjs": "0.13.4",
|
||||
"@tanstack/react-query": "5.90.8",
|
||||
"@tanstack/react-query-devtools": "5.90.2",
|
||||
|
||||
17
bun.lock
17
bun.lock
@@ -113,7 +113,6 @@
|
||||
"@react-email/render": "2.0.0",
|
||||
"@sim/logger": "workspace:*",
|
||||
"@socket.io/redis-adapter": "8.3.0",
|
||||
"@streamdown/code": "1.1.1",
|
||||
"@t3-oss/env-nextjs": "0.13.4",
|
||||
"@tanstack/react-query": "5.90.8",
|
||||
"@tanstack/react-query-devtools": "5.90.2",
|
||||
@@ -1458,8 +1457,6 @@
|
||||
|
||||
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||
|
||||
"@streamdown/code": ["@streamdown/code@1.1.1", "", { "dependencies": { "shiki": "^3.19.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-i7HTNuDgZWb+VdrNVOam9gQhIc5MSSDXKWXgbUrn/4vSRaSMM+Rtl10MQj4wLWPNpF7p80waJsAqFP8HZfb0Jg=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||
|
||||
"@t3-oss/env-core": ["@t3-oss/env-core@0.13.4", "", { "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0-beta.0" }, "optionalPeers": ["typescript", "valibot", "zod"] }, "sha512-zVOiYO0+CF7EnBScz8s0O5JnJLPTU0lrUi8qhKXfIxIJXvI/jcppSiXXsEJwfB4A6XZawY/Wg/EQGKANi/aPmQ=="],
|
||||
@@ -4630,8 +4627,6 @@
|
||||
|
||||
"@socket.io/redis-adapter/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"@streamdown/code/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
|
||||
|
||||
"@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||
@@ -5298,18 +5293,6 @@
|
||||
|
||||
"@shikijs/rehype/shiki/@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="],
|
||||
|
||||
"@streamdown/code/shiki/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="],
|
||||
|
||||
"@streamdown/code/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="],
|
||||
|
||||
"@streamdown/code/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="],
|
||||
|
||||
"@streamdown/code/shiki/@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="],
|
||||
|
||||
"@streamdown/code/shiki/@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="],
|
||||
|
||||
"@streamdown/code/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
|
||||
|
||||
"@trigger.dev/core/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
||||
|
||||
"@trigger.dev/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-transformer": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ=="],
|
||||
|
||||
Reference in New Issue
Block a user