class Interface {
constructor(width) {
this.isVisible = true;
this.canvas = document.createElement('canvas');
d3.select(this.canvas).call(d3.zoom()
.scaleExtent([0.1, 8])
.on("zoom", ({transform}) => {this.transform = transform}));
this.transform = d3.zoomIdentity;
this.width = Math.round(width);
this.height = Math.round(this.width * 0.4);
this.dpi = window.devicePixelRatio;
this.canvas.width = Math.round(this.width * this.dpi);
this.canvas.height = Math.round(this.height * this.dpi);
this.canvas.style.width = this.width + 'px';
this.canvas.addEventListener('mousemove', this.onMouseMove);
this.canvas.addEventListener('click', this.onClick);
document.addEventListener('keydown', this.onKeyDown);
this.root = new TokenNode(this, null, '', 1, true);
this.mouseX = -1;
this.mouseY = -1;
this.demoMode = true;
this.requestReady = true;
this.lastCreated = null;
this.nodeCreated = false;
this.screenBbox = [0, 0, this.width, this.height];
this.tick();
}
onKeyDown = e => {
if (!this.isVisible) return;
if (e.key === 'Enter') {
this.demoMode = true;
};
}
onMouseMove = e => {
const canvasBbox = this.canvas.getBoundingClientRect();
const [x0, y0, w, h] = this.screenBbox;
this.mouseX = x0 + w * (e.clientX - canvasBbox.left) / this.width;
this.mouseY = y0 + h * (e.clientY - canvasBbox.top) / this.height;
}
onClick = e => {
if (!TokenNode.hovered) return;
const {parent} = TokenNode.hovered;
if (!parent) return;
for (const child of parent.allChildren) {
if (!child.isVisible) {
child.isVisible = true;
this.demoMode = false;
this.lastCreated = child;
break;
}
}
}
onRequestReady = () => {
if (!this.isVisible) return;
const source = TokenNode.hovered || this.root;
const fetchTokensLimit = 100;
function getTargetTokenNode(source) {
let target = source;
let i = 0;
for (; i<fetchTokensLimit; i++) {
if (!target.allChildren || target.allChildren.length === 0) break;
target = target.allChildren[0];
}
if (!target.isInScreen()) return null;
if (i === fetchTokensLimit) return null;
return target;
}
let target = getTargetTokenNode(source);
if (!target && this.lastCreated) {
target = getTargetTokenNode(this.lastCreated);
}
if (!target && this.demoMode) {
if (this.root.fullHeight > this.screenBbox[3]) {
this.demoMode = false;
}
const maxInvisible = this.root.findMaxInvisible();
if (maxInvisible) {
maxInvisible.isVisible = true;
this.lastCreated = maxInvisible;
this.nodeCreated = true;
}
}
if (target === null) {
this.requestReady = true;
} else {
target.loadState = 'LOADING';
const prefix = target.prefix.join('');
// console.log(prefix);
this.requestReady = false;
mockGptRequest(prefix, response => {
target.allChildren = response.map(([prob, token], tokenNum) => {
return new TokenNode(this, target, token, prob, tokenNum === 0);
});
target.loadState = 'LOADED';
this.nodeCreated = true;
this.requestReady = true;
});
}
}
tick = () => {
if (this.isVisible) {
window.requestAnimationFrame(() => this.tick());
}
this.screenBbox = [
-this.transform.x / this.transform.k,
-this.transform.y / this.transform.k,
this.width / this.transform.k,
this.height / this.transform.k,
];
this.canvas.style.cursor = TokenNode.hovered ? 'pointer' : 'default';
this.root.tick();
this.root.setBbox();
if (this.requestReady) {
this.onRequestReady();
if (this.nodeCreated) {
this.root.tick();
this.nodeCreated = false;
}
}
const ctx = this.canvas.getContext('2d');
ctx.save();
ctx.scale(this.dpi, this.dpi);
ctx.clearRect(0, 0, this.width, this.height);
ctx.translate(this.transform.x, this.transform.y);
ctx.scale(this.transform.k, this.transform.k);
// ctx.strokeStyle = 'black';
// ctx.lineWidth = 10;
// ctx.strokeRect(...this.screenBbox);
this.root.draw(ctx);
// ctx.fillStyle = 'gray';
// ctx.fillRect(this.mouseX - 5, this.mouseY - 5, 10, 10);
ctx.restore();
}
}