76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import { useRef } from 'react'
|
|
|
|
// Hook to handle the transition to red when tokens stop flowing.
|
|
// Driven by the parent's animation clock time instead of independent intervals,
|
|
// so it slows down when the terminal is blurred.
|
|
export function useStalledAnimation(
|
|
time: number,
|
|
currentResponseLength: number,
|
|
hasActiveTools = false,
|
|
reducedMotion = false,
|
|
): {
|
|
isStalled: boolean
|
|
stalledIntensity: number
|
|
} {
|
|
const lastTokenTime = useRef(time)
|
|
const lastResponseLength = useRef(currentResponseLength)
|
|
const mountTime = useRef(time)
|
|
const stalledIntensityRef = useRef(0)
|
|
const lastSmoothTime = useRef(time)
|
|
|
|
// Reset timer when new tokens arrive (check actual length change)
|
|
if (currentResponseLength > lastResponseLength.current) {
|
|
lastTokenTime.current = time
|
|
lastResponseLength.current = currentResponseLength
|
|
stalledIntensityRef.current = 0
|
|
lastSmoothTime.current = time
|
|
}
|
|
|
|
// Derive time since last token from animation clock
|
|
let timeSinceLastToken: number
|
|
if (hasActiveTools) {
|
|
timeSinceLastToken = 0
|
|
lastTokenTime.current = time
|
|
} else if (currentResponseLength > 0) {
|
|
timeSinceLastToken = time - lastTokenTime.current
|
|
} else {
|
|
timeSinceLastToken = time - mountTime.current
|
|
}
|
|
|
|
// Calculate stalled intensity based on time since last token
|
|
// Start showing red after 3 seconds of no new tokens (only when no tools are active)
|
|
const isStalled = timeSinceLastToken > 3000 && !hasActiveTools
|
|
const intensity = isStalled
|
|
? Math.min((timeSinceLastToken - 3000) / 2000, 1) // Fade over 2 seconds
|
|
: 0
|
|
|
|
// Smooth intensity transition driven by animation frame ticks
|
|
if (!reducedMotion && (intensity > 0 || stalledIntensityRef.current > 0)) {
|
|
const dt = time - lastSmoothTime.current
|
|
if (dt >= 50) {
|
|
const steps = Math.floor(dt / 50)
|
|
let current = stalledIntensityRef.current
|
|
for (let i = 0; i < steps; i++) {
|
|
const diff = intensity - current
|
|
if (Math.abs(diff) < 0.01) {
|
|
current = intensity
|
|
break
|
|
}
|
|
current += diff * 0.1
|
|
}
|
|
stalledIntensityRef.current = current
|
|
lastSmoothTime.current = time
|
|
}
|
|
} else {
|
|
stalledIntensityRef.current = intensity
|
|
lastSmoothTime.current = time
|
|
}
|
|
|
|
// When reducedMotion is enabled, use instant intensity change
|
|
const effectiveIntensity = reducedMotion
|
|
? intensity
|
|
: stalledIntensityRef.current
|
|
|
|
return { isStalled, stalledIntensity: effectiveIntensity }
|
|
}
|