mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-07-09 16:00:52 +00:00
fix(ui): keep streaming bash output pinned (#556)
## Summary - notify the existing virtual follow-list path after streaming bash ANSI output updates - restore the inner bash output scroll immediately after direct pre updates - apply the same coalesced notification pattern to final ANSI output ## Validation - npm run typecheck --workspace @codenomad/ui
This commit is contained in:
parent
9c8baf86ca
commit
8b5ffa1905
1 changed files with 62 additions and 6 deletions
|
|
@ -10,17 +10,36 @@ import { getBashToolSearchText } from "../search-text"
|
|||
function RunningBashOutput(props: {
|
||||
content: Accessor<string>
|
||||
scrollHelpers?: ToolScrollHelpers
|
||||
onContentRendered?: () => void
|
||||
}) {
|
||||
let preRef: HTMLPreElement | undefined
|
||||
let pendingRenderNotificationFrame: number | null = null
|
||||
const updater = createStableAnsiStreamUpdater()
|
||||
|
||||
const notifyContentRendered = () => {
|
||||
if (!props.onContentRendered || typeof requestAnimationFrame !== "function") return
|
||||
if (pendingRenderNotificationFrame !== null) {
|
||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
||||
}
|
||||
pendingRenderNotificationFrame = requestAnimationFrame(() => {
|
||||
pendingRenderNotificationFrame = null
|
||||
props.onContentRendered?.()
|
||||
})
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
const element = preRef
|
||||
if (!element) return
|
||||
updater.update(element, props.content())
|
||||
props.scrollHelpers?.restoreAfterRender()
|
||||
notifyContentRendered()
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (pendingRenderNotificationFrame !== null) {
|
||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
||||
pendingRenderNotificationFrame = null
|
||||
}
|
||||
preRef = undefined
|
||||
updater.reset()
|
||||
})
|
||||
|
|
@ -37,10 +56,49 @@ function RunningBashOutput(props: {
|
|||
)
|
||||
}
|
||||
|
||||
function FinalAnsiOutput(props: {
|
||||
html: string
|
||||
scrollHelpers?: ToolScrollHelpers
|
||||
onContentRendered?: () => void
|
||||
}) {
|
||||
let pendingRenderNotificationFrame: number | null = null
|
||||
|
||||
const notifyContentRendered = () => {
|
||||
if (!props.onContentRendered || typeof requestAnimationFrame !== "function") return
|
||||
if (pendingRenderNotificationFrame !== null) {
|
||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
||||
}
|
||||
pendingRenderNotificationFrame = requestAnimationFrame(() => {
|
||||
pendingRenderNotificationFrame = null
|
||||
props.onContentRendered?.()
|
||||
})
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
props.html
|
||||
props.scrollHelpers?.restoreAfterRender()
|
||||
notifyContentRendered()
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (pendingRenderNotificationFrame !== null) {
|
||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
||||
pendingRenderNotificationFrame = null
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="message-text tool-call-markdown" ref={props.scrollHelpers?.registerContainer}>
|
||||
<pre class="tool-call-content tool-call-ansi" dir="auto" innerHTML={props.html} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BashToolBody(props: {
|
||||
toolState: Accessor<ToolState | undefined>
|
||||
renderMarkdown: (options: { content: string }) => ReturnType<ToolRenderer["renderBody"]>
|
||||
scrollHelpers?: ToolScrollHelpers
|
||||
onContentRendered?: () => void
|
||||
}) {
|
||||
const state = createMemo(() => props.toolState())
|
||||
|
||||
|
|
@ -91,14 +149,12 @@ function BashToolBody(props: {
|
|||
fallback={
|
||||
<Show when={finalAnsiHtml()} fallback={finalMarkdown() ? props.renderMarkdown({ content: finalMarkdown()! as string }) : null}>
|
||||
{(html) => (
|
||||
<div class="message-text tool-call-markdown" ref={props.scrollHelpers?.registerContainer}>
|
||||
<pre class="tool-call-content tool-call-ansi" dir="auto" innerHTML={html()} />
|
||||
</div>
|
||||
<FinalAnsiOutput html={html()} scrollHelpers={props.scrollHelpers} onContentRendered={props.onContentRendered} />
|
||||
)}
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<RunningBashOutput content={joinedContent} scrollHelpers={props.scrollHelpers} />
|
||||
<RunningBashOutput content={joinedContent} scrollHelpers={props.scrollHelpers} onContentRendered={props.onContentRendered} />
|
||||
</Show>
|
||||
</Show>
|
||||
)
|
||||
|
|
@ -124,7 +180,7 @@ export const bashRenderer: ToolRenderer = {
|
|||
const timeoutLabel = `${timeout}ms`
|
||||
return `${baseTitle} · ${tGlobal("toolCall.renderer.bash.title.timeout", { timeout: timeoutLabel })}`
|
||||
},
|
||||
renderBody({ toolState, renderMarkdown, scrollHelpers }) {
|
||||
return <BashToolBody toolState={toolState} renderMarkdown={renderMarkdown as any} scrollHelpers={scrollHelpers} />
|
||||
renderBody({ toolState, renderMarkdown, scrollHelpers, onContentRendered }) {
|
||||
return <BashToolBody toolState={toolState} renderMarkdown={renderMarkdown as any} scrollHelpers={scrollHelpers} onContentRendered={onContentRendered} />
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue