summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 11:19:34 -0600
committerAdam <[email protected]>2025-12-29 11:19:40 -0600
commit77c837eb1ae34332607ac17e5a1a0d403dd3e8f7 (patch)
tree733c5dbebee5ed375098946774473dc5b7d97789
parentdb77cc9845f8f47d220c67018edb67d7ee7ef151 (diff)
downloadopencode-77c837eb1ae34332607ac17e5a1a0d403dd3e8f7.tar.gz
opencode-77c837eb1ae34332607ac17e5a1a0d403dd3e8f7.zip
fix(desktop): throttle markdown renders
-rw-r--r--packages/ui/src/components/message-part.tsx60
1 files changed, 55 insertions, 5 deletions
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 6fee7cd26..31103b35c 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -1,4 +1,15 @@
-import { Component, createEffect, createMemo, createSignal, For, Match, Show, Switch, type JSX } from "solid-js"
+import {
+ Component,
+ createEffect,
+ createMemo,
+ createSignal,
+ For,
+ Match,
+ Show,
+ Switch,
+ onCleanup,
+ type JSX,
+} from "solid-js"
import { Dynamic } from "solid-js/web"
import {
AssistantMessage,
@@ -80,6 +91,41 @@ export type PartComponent = Component<MessagePartProps>
export const PART_MAPPING: Record<string, PartComponent | undefined> = {}
+const TEXT_RENDER_THROTTLE_MS = 250
+
+function createThrottledValue(getValue: () => string) {
+ const [value, setValue] = createSignal(getValue())
+ let timeout: ReturnType<typeof setTimeout> | undefined
+ let last = 0
+
+ createEffect(() => {
+ const next = getValue()
+ const now = Date.now()
+ const remaining = TEXT_RENDER_THROTTLE_MS - (now - last)
+ if (remaining <= 0) {
+ if (timeout) {
+ clearTimeout(timeout)
+ timeout = undefined
+ }
+ last = now
+ setValue(next)
+ return
+ }
+ if (timeout) clearTimeout(timeout)
+ timeout = setTimeout(() => {
+ last = Date.now()
+ setValue(next)
+ timeout = undefined
+ }, remaining)
+ })
+
+ onCleanup(() => {
+ if (timeout) clearTimeout(timeout)
+ })
+
+ return value
+}
+
function relativizeProjectPaths(text: string, directory?: string) {
if (!text) return ""
if (!directory) return text
@@ -464,11 +510,12 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
const data = useData()
const part = props.part as TextPart
const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
+ const throttledText = createThrottledValue(displayText)
return (
- <Show when={displayText()}>
+ <Show when={throttledText()}>
<div data-component="text-part">
- <Markdown text={displayText()} />
+ <Markdown text={throttledText()} />
</div>
</Show>
)
@@ -476,10 +523,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) {
const part = props.part as ReasoningPart
+ const text = () => part.text.trim()
+ const throttledText = createThrottledValue(text)
+
return (
- <Show when={part.text.trim()}>
+ <Show when={throttledText()}>
<div data-component="reasoning-part">
- <Markdown text={part.text.trim()} />
+ <Markdown text={throttledText()} />
</div>
</Show>
)