@@ -13,6 +13,10 @@ interface DsMarkdownContentProps { const STREAMING_INTERVAL = 20 +function isWaitingPlaceholder(content: string) { + return content.startsWith('Ваш вопрос получен. Ожидание ответа') +} + function getNextRevealLength(currentLength: number, target: string): number { if (currentLength >= target.length) { return target.length @@ -41,7 +45,11 @@ function getCommonPrefixLength(a: string, b: string): number { export function DsMarkdownContent({ content, streaming }: DsMarkdownContentProps) { const sessionRef = useRef(streaming) - const [displayed, setDisplayed] = useState(content) + // If the whole answer arrives in one paint (common when SSE chunks are buffered), + // start empty so the typewriter still runs instead of dumping the full string. + const [displayed, setDisplayed] = useState(() => + streaming && content && !isWaitingPlaceholder(content) ? '' : content + ) useEffect(() => { if (streaming) { @@ -271,9 +271,18 @@ async function consumeSseStream( return } } + + await Promise.resolve() } } +function endStreamLoading(setLoading: (value: boolean) => void, onAfter?: () => void) { + window.setTimeout(() => { + setLoading(false) + onAfter?.() + }, 0) +} + export function useChatModel( currentChat: string | null, showMessage: (message: string, variant?: Variant) => void, @@ -636,11 +645,12 @@ export function useChatModel( return false } finally { isSendingRef.current = false - setLoading(false) - dispatch(getUserBalance(data.access)) if (abortRef.current === abortController) { abortRef.current = null } + endStreamLoading(setLoading, () => { + dispatch(getUserBalance(data.access)) + }) } }, [data?.access, dispatch, modelType, persistStreamSession, processMessageStream, resetStreamRuntime, showMessage] @@ -986,11 +996,12 @@ export function useChatModel( showMessage('Непредвиденная ошибка, попробуйте еще раз') } finally { isSendingRef.current = false - setLoading(false) - dispatch(getUserBalance(data.access)) if (abortRef.current === abortController) { abortRef.current = null } + endStreamLoading(setLoading, () => { + dispatch(getUserBalance(data.access)) + }) } }, [