diff --git a/ui-tui/src/app/scroll.ts b/ui-tui/src/app/scroll.ts index 2572e2808f..0d736d2c87 100644 --- a/ui-tui/src/app/scroll.ts +++ b/ui-tui/src/app/scroll.ts @@ -1,7 +1,6 @@ import type { ScrollBoxHandle } from '@hermes/ink' import type { SelectionApi } from './interfaces.js' -import { markScrolling } from './interactionMode.js' export interface SelectionSnap { anchor?: { row: number } | null @@ -30,8 +29,6 @@ export function scrollWithSelectionBy(delta: number, { scrollRef, selection }: S return } - markScrolling() - const sel = selection.getState() as null | SelectionSnap const top = s.getViewportTop() const bottom = top + viewport - 1 diff --git a/ui-tui/src/app/turnController.ts b/ui-tui/src/app/turnController.ts index bc40deba2c..3240c4e89c 100644 --- a/ui-tui/src/app/turnController.ts +++ b/ui-tui/src/app/turnController.ts @@ -2,7 +2,6 @@ import { REASONING_PULSE_MS, STREAM_BATCH_MS, STREAM_IDLE_BATCH_MS, - STREAM_SCROLLING_BATCH_MS, STREAM_TYPING_BATCH_MS } from '../config/timing.js' import type { SessionInterruptResponse, SubagentEventPayload } from '../gatewayTypes.js' @@ -16,7 +15,6 @@ import { } from '../lib/text.js' import type { ActiveTool, ActivityItem, Msg, SubagentProgress } from '../types.js' -import { getInteractionMode } from './interactionMode.js' import { resetFlowOverlays } from './overlayStore.js' import { pushSnapshot } from './spawnHistoryStore.js' import { getTurnState, patchTurnState, resetTurnState } from './turnStore.js' @@ -504,15 +502,12 @@ class TurnController { return } - const interaction = getInteractionMode() - const delay = interaction === 'scrolling' ? STREAM_SCROLLING_BATCH_MS : interaction === 'typing' ? STREAM_TYPING_BATCH_MS : this.streamDelay - this.streamTimer = setTimeout(() => { this.streamTimer = null const raw = this.bufRef.trimStart() const visible = hasReasoningTag(raw) ? splitReasoning(raw).text : raw patchTurnState({ streaming: visible }) - }, delay) + }, this.streamDelay) } startMessage() { diff --git a/ui-tui/src/app/useSubmission.ts b/ui-tui/src/app/useSubmission.ts index 8e5f15c1f9..9bca65815d 100644 --- a/ui-tui/src/app/useSubmission.ts +++ b/ui-tui/src/app/useSubmission.ts @@ -1,5 +1,6 @@ import { type MutableRefObject, useCallback, useEffect, useRef } from 'react' +import { TYPING_IDLE_MS } from '../config/timing.js' import { attachedImageNotice } from '../domain/messages.js' import { looksLikeSlashCommand } from '../domain/slash.js' import type { GatewayClient } from '../gatewayClient.js' @@ -10,7 +11,6 @@ import { PASTE_SNIPPET_RE } from '../protocol/paste.js' import type { Msg } from '../types.js' import type { ComposerActions, ComposerRefs, ComposerState, PasteSnippet } from './interfaces.js' -import { markTyping } from './interactionMode.js' import { turnController } from './turnController.js' import { getUiState, patchUiState } from './uiStore.js' @@ -48,13 +48,28 @@ export function useSubmission(opts: UseSubmissionOptions) { } = opts const lastEmptyAt = useRef(0) + const typingIdleTimer = useRef | null>(null) useEffect(() => { if (composerState.input || composerState.inputBuf.length) { - markTyping() if (getUiState().busy) { turnController.boostStreamingForTyping() } + + if (typingIdleTimer.current) { + clearTimeout(typingIdleTimer.current) + } + + typingIdleTimer.current = setTimeout(() => { + typingIdleTimer.current = null + turnController.relaxStreaming() + }, TYPING_IDLE_MS) + } + + return () => { + if (typingIdleTimer.current) { + clearTimeout(typingIdleTimer.current) + } } }, [composerState.input, composerState.inputBuf]) diff --git a/ui-tui/src/config/timing.ts b/ui-tui/src/config/timing.ts index 083fa17f7f..d428bacfee 100644 --- a/ui-tui/src/config/timing.ts +++ b/ui-tui/src/config/timing.ts @@ -1,7 +1,6 @@ export const STREAM_BATCH_MS = 16 export const STREAM_IDLE_BATCH_MS = 16 -export const STREAM_SCROLLING_BATCH_MS = 250 -export const STREAM_TYPING_BATCH_MS = 120 +export const STREAM_TYPING_BATCH_MS = 80 export const TYPING_IDLE_MS = 250 export const SCROLLING_IDLE_MS = 450 export const REASONING_PULSE_MS = 700