// Pace already received text while preserving network completion and cancellation. var ChatReply = (function(){ function create(options){ const now = options.now || Date.now; const readyAt = now() + 300; const segmenter = typeof Intl !== 'undefined' && Intl.Segmenter ? new Intl.Segmenter(undefined, {granularity:'grapheme'}) : null; let text = '', characters = [], visible = 0; let startedAt = null, timer = null, card = null; let complete = false, closed = false; function clearTimer(){ if(timer !== null) clearTimeout(timer); timer = null; } function schedule(delay){ if(timer === null && !closed) timer = setTimeout(function(){ try{ draw(false); } catch(error){ closed = true; clearTimer(); options.onError(error); } }, delay); } function draw(force){ clearTimer(); if(closed) return; const animate = !force && options.shouldAnimate(); if(animate && now() < readyAt){ schedule(readyAt - now()); return; } if(card){ const show = card; card = null; show(); } if(visible < characters.length){ if(startedAt === null) startedAt = now(); const elapsed = Math.max(0, now() - startedAt); const rate = Math.max(45, characters.length / 4); visible = animate ? Math.min(characters.length, Math.max(visible + 1, Math.floor(elapsed * rate / 1000))) : characters.length; options.onText(characters.slice(0, visible).join('')); } if(visible < characters.length){ schedule(16); return; } if(complete){ closed = true; options.onComplete(); } } function update(){ if(closed) return; if(!options.shouldAnimate()) draw(true); else schedule(Math.max(0, readyAt - now())); } function setText(value){ text = value; characters = segmenter ? Array.from(segmenter.segment(text), item => item.segment) : Array.from(text); } return { append:function(value){ if(closed) return; card = null; setText(text + value); update(); }, replace:function(value){ if(closed) return; card = null; setText(value); visible = 0; update(); }, card:function(show){ if(closed) return; setText(''); visible = 0; card = show; update(); }, complete:function(){ complete = true; update(); }, flush:function(){ draw(true); }, cancel:function(){ closed = true; clearTimer(); }, }; } return {create:create}; })();