82 lines
3.0 KiB
JavaScript
82 lines
3.0 KiB
JavaScript
// 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};
|
|
})();
|