Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support rendering partial markdown and improve performance #73

Open
wants to merge 6 commits into
base: promplate-demo
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@
"highlight.js": "^11.10.0",
"katex": "^0.16.11",
"lenis": "^1.1.13",
"markdown-it": "^14.1.0",
"markdown-it-highlightjs": "^4.2.0",
"markdown-it-katex": "^2.0.3",
"partial-json": "^0.1.7",
"rehype-highlight": "^7.0.0",
"rehype-katex": "^7.0.1",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"remark-parse": "^11.0.0",
"reveal.js": "^5.1.0",
"solid-js": "^1.9.1",
"solid-markdown": "^2.0.13",
"solid-toast": "^0.5.0",
"solidjs-use": "^2.3.0",
"svelte": "^4.2.19",
"svelte-sonner": "^0.3.28",
"tiktoken": "^1.0.16"
"tiktoken": "^1.0.16",
"unified": "^11.0.5"
},
"devDependencies": {
"@evan-yang/eslint-config": "^1.0.9",
Expand Down
811 changes: 741 additions & 70 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions src/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useClipboard } from 'solidjs-use'
import type { SolidMarkdownComponents } from 'solid-markdown'

const CodeBlock: SolidMarkdownComponents['code'] = ({ children, inline }) => {
if (inline)
return <code>{children}</code>

const source = String(children)

const { copy, copied } = useClipboard({ copiedDuring: 1000 })

return (
<code class="group hljs block w-full overflow-x-scroll px-20px py-18px">
<button onClick={() => copy(source)} class="gap-1 text-sm gpt-copy-btn">
{copied() && <div class="text-sm font-sans">Copied!</div>}
<div class={copied() ? 'i-mingcute-copy-2-fill' : 'i-mingcute-copy-2-line'} />
</button>
{children}
</code>
)
}

export default CodeBlock
74 changes: 24 additions & 50 deletions src/components/MessageItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { createSignal } from 'solid-js'
import MarkdownIt from 'markdown-it'
import mdKatex from 'markdown-it-katex'
import mdHighlight from 'markdown-it-highlightjs'
import { useClipboard, useEventListener } from 'solidjs-use'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import rehypeHighlight from 'rehype-highlight'
import { SolidMarkdown } from 'solid-markdown'
import remarkBreaks from 'remark-breaks'
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import IconRefresh from './icons/Refresh'
import CodeBlock from './CodeBlock'
import type { Accessor } from 'solid-js'
import type { ChatMessage } from '@/types'

Expand All @@ -22,22 +26,6 @@ export default ({ role, message, showRetry, onRetry }: Props) => {
user: 'bg-$c-fg-30',
assistant: 'bg-emerald-600/50 dark:bg-emerald-300 sm:(bg-gradient-to-br from-cyan-200 to-green-200)',
}
const [source] = createSignal('')
const { copy, copied } = useClipboard({ source, copiedDuring: 1000 })

useEventListener('click', (e) => {
const el = e.target as HTMLElement
let code = null

if (el.matches('[data-code]')) {
code = decodeURIComponent(el.dataset.code!)
copy(code)
}
if (el.matches('[data-code] > div')) {
code = decodeURIComponent(el.parentElement!.dataset.code!)
copy(code)
}
})

function heuristicPatch(markdown: string) {
const pattern = /(^|\n)```\S*$/
Expand All @@ -48,41 +36,27 @@ export default ({ role, message, showRetry, onRetry }: Props) => {
: markdown
}

const htmlString = () => {
const md = MarkdownIt({
linkify: true,
breaks: true,
}).use(mdKatex).use(mdHighlight)
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const [tokens, idx] = args
const token = tokens[idx]
const rawCode = fence(...args)

return `<div class="relative group">
<div data-code=${encodeURIComponent(token.content)} class="gpt-copy-btn">
${copied() ? '<div mr-1 text-sm display-inline-block>Copied!</div><div i-mingcute-copy-2-fill></div>' : '<div i-mingcute-copy-2-line></div>'}
</div>
${rawCode}
</div>`
}

switch (typeof message) {
case 'function':
return md.render(heuristicPatch(message()))
case 'string':
return md.render(heuristicPatch(message))
default:
return ''
}
}
unified().use(remarkParse) // a weird workaround to fix a strange bug in solid-markdown that "setting on undefined" error

return (
<div class="px-20 transition-colors -mx-20 hover:bg-$c-fg-2 2xl:(px-20 -mx-20) md:(px-5 transition-background-color -mx-5)">
<div class="py-0.5 transition-padding 2xl:py-2 md:py-1">
<div class="flex gap-3.5 rounded-lg" class:op-75={role === 'user'} class:reverse-self-msg={role === 'user' && alignRightMine}>
<div class={`shrink-0 w-7 h-7 my-4 rounded-full op-80 ${roleClass[role]} <sm:w-1 <sm:h-auto <md:transition-background-color`} />
<div class="relative max-w-full overflow-hidden break-words prose <sm:text-3.6 message" innerHTML={htmlString()} />
<SolidMarkdown
renderingStrategy="reconcile"
remarkPlugins={[remarkGfm, remarkBreaks, remarkMath]}
rehypePlugins={[rehypeHighlight, rehypeKatex]}
class="relative max-w-full overflow-hidden break-words prose <sm:text-3.6 message"
components={{
code: CodeBlock,
pre({ children }) {
return <pre class="group overflow-hidden">{children}</pre>
},
}}
>
{heuristicPatch(typeof message === 'function' ? message() : message)}
</SolidMarkdown>
</div>
CNSeniorious000 marked this conversation as resolved.
Show resolved Hide resolved
{showRetry?.() && onRetry && (
<div class="mb-2 fie px-3">
Expand Down
3 changes: 2 additions & 1 deletion src/message.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.message pre {
background-color: #24242d;
font-size: 0.8rem;
padding: 0.4rem 0.6rem;
padding: 0;
position: relative;
}

.dark .message pre {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class API {

const res = await fetch(`${promplateBaseUrl}/single/suggest`, {
method: 'PUT',
body: JSON.stringify({ messages, model: 'llama3-70b-8192', prefill: true }),
body: JSON.stringify({ messages, model: 'llama-3.2-90b-text-preview', prefill: true }),
headers: { 'content-type': 'application/json' },
})

Expand Down
2 changes: 1 addition & 1 deletion uno.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default defineConfig({
'b-slate-link': 'border-b border-($c-fg-70 none) hover:border-dashed',
'gpt-title': 'text-2xl font-extrabold',
'gpt-subtitle': 'text-(2xl transparent) font-extrabold bg-(clip-text gradient-to-r) from-emerald-400 to-sky-200',
'gpt-copy-btn': 'absolute top-12px right-12px z-3 fcc border b-transparent w-fit min-w-8 h-8 p-2 bg-white/4 hover:bg-white/8 text-white/85 dark:(bg-$c-fg-5 hover:bg-$c-fg-10 text-$c-fg-90) cursor-pointer transition-all duration-150 active:scale-90 op-0 group-hover:op-100 rounded-1.1 backdrop-blur-10',
'gpt-copy-btn': 'absolute top-12px right-12px z-3 fcc border b-transparent h-8 p-2 bg-white/4 hover:bg-white/8 text-white/85 dark:(bg-$c-fg-5 hover:bg-$c-fg-10 text-$c-fg-90) transition-all duration-150 active:scale-90 op-0 group-hover:op-100 rounded-1.1 backdrop-blur-10',
'gpt-retry-btn': 'fi gap-1 px-2 py-0.5 op-70 ring-1.2 ring-$c-fg-50 rounded-md text-sm <sm:text-xs <sm:ring-$c-fg-30 cursor-pointer hover:bg-$c-fg-5 transition-background-color',
'gpt-back-top-btn': 'fcc text-base fixed bottom-17px right-17px sm:(bottom-20px right-20px) z-10 cursor-pointer',
'gpt-password-input': 'px-4 py-3 h-12 rounded-sm bg-(slate op-15) base-focus',
Expand Down
Loading