SPB Git

spb/zyquo-cloud-web Public MIT

Zyquo Cloud Web — every cloud model, one beautiful chat, entirely in your browser.

TypeScript 81.9% CSS 8.9% JavaScript 7.5% Shell 1.1% HTML 0.6%
3.7 KB · 111 lines tsx
Raw Blame History
1/*2 *  Markdown.tsx3 *  Zyquo Cloud Web4 *5 *  Author: Simon-Pierre Boucher6 *  Mail: contact@spboucher.ai7 *8 *  Assistant message rendering: GitHub-flavored Markdown with syntax-9 *  highlighted code blocks (language label, copy, wrap toggle) mapped to the10 *  Zyquo syntax tokens. Memoized — re-parsing only when text changes.11 */1213import { lazy, memo, Suspense, useState, type ReactNode } from 'react'14import ReactMarkdown from 'react-markdown'15import remarkGfm from 'remark-gfm'16import remarkMath from 'remark-math'17import rehypeHighlight from 'rehype-highlight'18import rehypeKatex from 'rehype-katex'19import 'katex/dist/katex.min.css'20import { IconCheck, IconCopy } from './icons'2122const Mermaid = lazy(() => import('./Mermaid'))2324function extractText(node: ReactNode): string {25  if (typeof node === 'string') return node26  if (Array.isArray(node)) return node.map(extractText).join('')27  if (node && typeof node === 'object' && 'props' in node) {28    return extractText((node as { props: { children?: ReactNode } }).props.children)29  }30  return ''31}3233function CodeBlock({ className, children }: { className?: string; children?: ReactNode }) {34  const [copied, setCopied] = useState(false)35  const [wrap, setWrap] = useState(false)36  const language = /language-(\w+)/.exec(className ?? '')?.[1] ?? ''3738  const copy = () => {39    void navigator.clipboard.writeText(extractText(children).replace(/\n$/, ''))40    setCopied(true)41    setTimeout(() => setCopied(false), 1200)42  }4344  return (45    <div className="code-block">46      <div className="code-block-header">47        <span>{language || 'text'}</span>48        <span className={`code-block-actions${copied ? ' confirming' : ''}`}>49          <button className="code-block-copy" onClick={() => setWrap((w) => !w)}>50            {wrap ? 'unwrap' : 'wrap'}51          </button>52          <button className={`code-block-copy${copied ? ' ok' : ''}`} onClick={copy}>53            {copied ? <IconCheck size={10} /> : <IconCopy size={10} />} {copied ? 'copied' : 'copy'}54          </button>55        </span>56      </div>57      <pre className={wrap ? 'wrap' : ''}>58        <code className={className}>{children}</code>59      </pre>60    </div>61  )62}6364export const Markdown = memo(function Markdown({ text }: { text: string }) {65  return (66    <div className="markdown">67      <ReactMarkdown68        remarkPlugins={[remarkGfm, remarkMath]}69        rehypePlugins={[70          [rehypeHighlight, { detect: false, ignoreMissing: true }],71          [rehypeKatex, { errorColor: 'var(--z-danger)' }],72        ]}73        components={{74          pre: ({ children }) => <>{children}</>,75          code: ({ className, children, ...props }) => {76            const inline = !className && typeof children === 'string' && !children.includes('\n')77            if (inline) {78              return (79                <code className={className} {...props}>80                  {children}81                </code>82              )83            }84            if (/language-mermaid/.test(className ?? '')) {85              return (86                <Suspense87                  fallback={88                    <div style={{ color: 'var(--z-text-tertiary)', fontSize: 12 }}>89                      Rendering diagram…90                    </div>91                  }92                >93                  <Mermaid code={extractText(children).replace(/\n$/, '')} />94                </Suspense>95              )96            }97            return <CodeBlock className={className}>{children}</CodeBlock>98          },99          a: ({ href, children }) => (100            <a href={href} target="_blank" rel="noreferrer noopener">101              {children}102            </a>103          ),104        }}105      >106        {text}107      </ReactMarkdown>108    </div>109  )110})111