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 { marked } from 'marked'
|
||||||
|
|
||||||
import mermaid from 'mermaid'
|
import mermaid from 'mermaid'
|
||||||
|
|
@ -201,6 +201,15 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
||||||
const [renaming, setRenaming] = useState(false)
|
const [renaming, setRenaming] = useState(false)
|
||||||
const [renameVal, setRenameVal] = useState('')
|
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 startRename = () => { setRenaming(true); setRenameVal(sid) }
|
||||||
const commitRename = () => {
|
const commitRename = () => {
|
||||||
setRenaming(false)
|
setRenaming(false)
|
||||||
|
|
@ -251,7 +260,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
||||||
<div key={i} class={`message ${msg.role}`}>
|
<div key={i} class={`message ${msg.role}`}>
|
||||||
<div class="msg-bubble">
|
<div class="msg-bubble">
|
||||||
{msg.role === 'user' ? (
|
{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' ? (
|
) : 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 : ''} />
|
<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' ? (
|
) : msg.role === 'tool' ? (
|
||||||
|
|
@ -265,7 +274,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
class="md"
|
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>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,11 @@ export function PromptBar({ running, onSend, onStop }: Props) {
|
||||||
const handleInput = (e: Event) => {
|
const handleInput = (e: Event) => {
|
||||||
const el = e.target as HTMLTextAreaElement
|
const el = e.target as HTMLTextAreaElement
|
||||||
setValue(el.value)
|
setValue(el.value)
|
||||||
el.style.height = 'auto'
|
// Use requestAnimationFrame to batch the resize reflow
|
||||||
el.style.height = Math.min(el.scrollHeight, 160) + 'px'
|
requestAnimationFrame(() => {
|
||||||
|
el.style.height = '0'
|
||||||
|
el.style.height = Math.min(el.scrollHeight, 160) + 'px'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Reference in New Issue