perf: memoize message HTML to avoid re-parsing on every keystroke
marked.parse runs on all messages each render. With long conversations this makes typing sluggish. useMemo caches rendered HTML per message, only recomputing when messages or cwd change. Also deferred textarea resize to requestAnimationFrame.
This commit is contained in:
parent
aa0e99213d
commit
0f97e50a49
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { useEffect, useRef, useState, useMemo } from 'preact/hooks'
|
||||
import { marked } from 'marked'
|
||||
|
||||
import mermaid from 'mermaid'
|
||||
|
|
@ -201,6 +201,15 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
|||
const [renaming, setRenaming] = useState(false)
|
||||
const [renameVal, setRenameVal] = useState('')
|
||||
|
||||
// Pre-render message HTML so typing in PromptBar doesn't re-parse everything
|
||||
const renderedHtml = useMemo(() =>
|
||||
messages.map(msg =>
|
||||
(msg.role === 'user' || msg.role === 'assistant')
|
||||
? linkifyPaths((marked.parse(msg.content) as string), session.cwd)
|
||||
: ''),
|
||||
[messages, session.cwd]
|
||||
)
|
||||
|
||||
const startRename = () => { setRenaming(true); setRenameVal(sid) }
|
||||
const commitRename = () => {
|
||||
setRenaming(false)
|
||||
|
|
@ -251,7 +260,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
|||
<div key={i} class={`message ${msg.role}`}>
|
||||
<div class="msg-bubble">
|
||||
{msg.role === 'user' ? (
|
||||
<div class="md" dangerouslySetInnerHTML={{ __html: linkifyPaths((marked.parse(msg.content) as string).replace(/^<p>/, '<p><span class="msg-prefix">u: </span>'), session.cwd) }} />
|
||||
<div class="md" dangerouslySetInnerHTML={{ __html: renderedHtml[i].replace(/^<p>/, '<p><span class="msg-prefix">u: </span>') }} />
|
||||
) : msg.role === 'call' ? (
|
||||
<ToolCall name={msg.name ?? ''} args={msg.content} output={messages[i+1]?.role === 'tool' && messages[i+1]?.name === msg.name ? messages[i+1].content : ''} />
|
||||
) : msg.role === 'tool' ? (
|
||||
|
|
@ -265,7 +274,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
|||
) : (
|
||||
<div
|
||||
class="md"
|
||||
dangerouslySetInnerHTML={{ __html: linkifyPaths((marked.parse(msg.content) as string).replace(/^<p>/, '<p><span class="msg-prefix">a: </span>'), session.cwd) }}
|
||||
dangerouslySetInnerHTML={{ __html: renderedHtml[i].replace(/^<p>/, '<p><span class="msg-prefix">a: </span>') }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@ export function PromptBar({ running, onSend, onStop }: Props) {
|
|||
const handleInput = (e: Event) => {
|
||||
const el = e.target as HTMLTextAreaElement
|
||||
setValue(el.value)
|
||||
el.style.height = 'auto'
|
||||
el.style.height = Math.min(el.scrollHeight, 160) + 'px'
|
||||
// Use requestAnimationFrame to batch the resize reflow
|
||||
requestAnimationFrame(() => {
|
||||
el.style.height = '0'
|
||||
el.style.height = Math.min(el.scrollHeight, 160) + 'px'
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
Reference in New Issue