|
@@ -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 { Dynamic } from "solid-js/web"
|
|
|
import {
|
|
import {
|
|
|
AssistantMessage,
|
|
AssistantMessage,
|
|
@@ -80,6 +91,41 @@ export type PartComponent = Component<MessagePartProps>
|
|
|
|
|
|
|
|
export const PART_MAPPING: Record<string, PartComponent | undefined> = {}
|
|
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) {
|
|
function relativizeProjectPaths(text: string, directory?: string) {
|
|
|
if (!text) return ""
|
|
if (!text) return ""
|
|
|
if (!directory) return text
|
|
if (!directory) return text
|
|
@@ -464,11 +510,12 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
|
|
const data = useData()
|
|
const data = useData()
|
|
|
const part = props.part as TextPart
|
|
const part = props.part as TextPart
|
|
|
const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
|
|
const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
|
|
|
|
|
+ const throttledText = createThrottledValue(displayText)
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
- <Show when={displayText()}>
|
|
|
|
|
|
|
+ <Show when={throttledText()}>
|
|
|
<div data-component="text-part">
|
|
<div data-component="text-part">
|
|
|
- <Markdown text={displayText()} />
|
|
|
|
|
|
|
+ <Markdown text={throttledText()} />
|
|
|
</div>
|
|
</div>
|
|
|
</Show>
|
|
</Show>
|
|
|
)
|
|
)
|
|
@@ -476,10 +523,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
|
|
|
|
|
|
|
PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) {
|
|
PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) {
|
|
|
const part = props.part as ReasoningPart
|
|
const part = props.part as ReasoningPart
|
|
|
|
|
+ const text = () => part.text.trim()
|
|
|
|
|
+ const throttledText = createThrottledValue(text)
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
- <Show when={part.text.trim()}>
|
|
|
|
|
|
|
+ <Show when={throttledText()}>
|
|
|
<div data-component="reasoning-part">
|
|
<div data-component="reasoning-part">
|
|
|
- <Markdown text={part.text.trim()} />
|
|
|
|
|
|
|
+ <Markdown text={throttledText()} />
|
|
|
</div>
|
|
</div>
|
|
|
</Show>
|
|
</Show>
|
|
|
)
|
|
)
|