mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
perf(desktop): stop tool rows re-rendering on session/cwd change + memo leaves
Two tool-render wins during streaming / on session switch: 1. Every ToolEntry did useStore($activeSessionId)+useStore($currentCwd), so any session or cwd change re-rendered *every* mounted tool row — but they're only read inside the preview-artifact effect. Read .get() at fire time instead (the effect only runs when a previewable target appears); no subscription. 2. memo() AnsiText + CompactMarkdown. Their text props are string values (value-equal across renders), so memo skips the re-render — and the per-tick ANSI parse / Streamdown re-run — when a parent ToolEntry re-renders on an unrelated stream delta. No behavior change. typecheck + eslint clean; tool fallback tests green (30).
This commit is contained in:
parent
3e23c502f2
commit
fb2a35c0b5
3 changed files with 22 additions and 11 deletions
|
|
@ -1,5 +1,4 @@
|
|||
import type { FC } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { ansiColorClass, hasAnsiCodes, parseAnsi } from '@/lib/ansi'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
|
@ -12,7 +11,7 @@ interface AnsiTextProps {
|
|||
/** Renders text with embedded ANSI SGR codes as colored / bold spans. Falls
|
||||
* back to a plain string node when no codes are present so the parser cost
|
||||
* is paid only when there's something to colorize. */
|
||||
export const AnsiText: FC<AnsiTextProps> = ({ className, text }) => {
|
||||
export const AnsiText = memo(({ className, text }: AnsiTextProps) => {
|
||||
const segments = useMemo(() => (hasAnsiCodes(text) ? parseAnsi(text) : null), [text])
|
||||
|
||||
if (!segments) {
|
||||
|
|
@ -31,4 +30,4 @@ export const AnsiText: FC<AnsiTextProps> = ({ className, text }) => {
|
|||
))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -311,17 +311,22 @@ function ToolEntry({ part }: ToolEntryProps) {
|
|||
// in the composer status stack rather than a bulky inline card. Uses the same
|
||||
// detected target the old inline card did, keyed to the active session the
|
||||
// stack reads from. Idempotent + dedup'd, so re-renders don't churn.
|
||||
const activeSessionId = useStore($activeSessionId)
|
||||
const currentCwd = useStore($currentCwd)
|
||||
const previewTarget = view.previewTarget
|
||||
|
||||
useEffect(() => {
|
||||
if (isPending || !activeSessionId || !previewTarget || !isPreviewableTarget(previewTarget)) {
|
||||
if (isPending || !previewTarget || !isPreviewableTarget(previewTarget)) {
|
||||
return
|
||||
}
|
||||
|
||||
recordPreviewArtifact(activeSessionId, previewTarget, currentCwd || '')
|
||||
}, [activeSessionId, currentCwd, isPending, previewTarget])
|
||||
// Read (don't subscribe) session/cwd: this only fires when a previewable
|
||||
// target appears, and subscribing re-rendered every tool row on any session
|
||||
// or cwd change.
|
||||
const activeSessionId = $activeSessionId.get()
|
||||
|
||||
if (activeSessionId) {
|
||||
recordPreviewArtifact(activeSessionId, previewTarget, $currentCwd.get() || '')
|
||||
}
|
||||
}, [isPending, previewTarget])
|
||||
|
||||
const detailSections = useMemo(() => {
|
||||
if (!view.detail) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { ComponentProps, ElementType, FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { Streamdown } from 'streamdown'
|
||||
|
||||
import { ExternalLink, ExternalLinkIcon } from '@/lib/external-link'
|
||||
|
|
@ -102,7 +103,13 @@ const COMPONENTS = {
|
|||
ul: tagged('ul')
|
||||
}
|
||||
|
||||
export function CompactMarkdown({ className, text }: { className?: string; text: string }) {
|
||||
export const CompactMarkdown = memo(function CompactMarkdown({
|
||||
className,
|
||||
text
|
||||
}: {
|
||||
className?: string
|
||||
text: string
|
||||
}) {
|
||||
return (
|
||||
<div className={cn('max-w-full text-xs leading-relaxed text-muted-foreground/90 wrap-anywhere', className)}>
|
||||
<Streamdown components={COMPONENTS} controls={false} mode="static" parseIncompleteMarkdown={false}>
|
||||
|
|
@ -110,4 +117,4 @@ export function CompactMarkdown({ className, text }: { className?: string; text:
|
|||
</Streamdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue