Public
Edited
Sep 26, 2023
Insert cell
Insert cell
Insert cell
mockGptRequest('hello', console.log)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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();
}
}
Insert cell
class TokenNode {
static hovered = null;
static nodesVPad = 5;
constructor(int, parent, token, prob=1, isVisible=false) {
this.int = int;
this.prob = prob;
this.dx = 20;
this.dy = 20;
this.isVisible = isVisible;
this.parent = parent;
this.token = token;
this.fontSize = 20;
this.tokenWidth = token.length * this.fontSize * 0.6;
this.tokenHeight = this.fontSize * 1.2;
this.fullHeight = this.tokenHeight;
this.linesPadLeft = 0;
this.prefix = [];
this.level = 0;
let cur = this;
while (cur.parent) {
this.level += 1;
this.prefix.unshift(cur.token);
cur = cur.parent;
}
this.allChildren = null;
this.loadState = 'NOT_LOADED';
}
forEachVisibleChild(fun) {
if (!this.allChildren) return;
let childNum = 0;
return this.allChildren.forEach(child => {
if (!child.isVisible) return;
fun(child, childNum);
childNum += 1;
});
}
tick() {
if (this.parent === null) TokenNode.hovered = null;
this.childrenCount = 0;
this.forEachVisibleChild(child => {
this.childrenCount += 1;
});
let targetLinesPadLeft = 20;
if (this.childrenCount <= 1) {
targetLinesPadLeft = 0;
}
this.linesPadLeft = this.linesPadLeft * 0.9 + targetLinesPadLeft * 0.1;
this.forEachVisibleChild(child => {
child.tick();
});
this.fullHeight = -TokenNode.nodesVPad;
this.forEachVisibleChild(child => {
child.dx = this.tokenWidth + this.linesPadLeft;
child.dy = TokenNode.nodesVPad + this.fullHeight;
child.setBbox();
const childHovered = isPtInRect([this.int.mouseX, this.int.mouseY], child.tokenBbox);
if (childHovered) {
TokenNode.hovered = child;
}
this.fullHeight += TokenNode.nodesVPad + child.fullHeight;
});
this.fullHeight = Math.max(
this.fullHeight,
this.tokenHeight,
);
}
setBbox() {
let x0 = 0, y0 = 0, cur = this;
while (true) {
x0 += cur.dx;
y0 += cur.dy;
cur = cur.parent;
if (!cur) break;
}
this.tokenBbox = [x0, y0, this.tokenWidth, this.tokenHeight];
}
isInScreen() {
if (!this.tokenBbox) return false;
return isRectInsideRect(this.tokenBbox, this.int.screenBbox);
}
findMaxInvisible() {
let maxProb = 0;
let maxProbNode = null;
if (!this.isVisible && (this.parent === null || this.parent.isInScreen())) {
maxProb = this.prob;
maxProbNode = this;
}
if (!this.allChildren) return maxProbNode;
for (const child of this.allChildren) {
const curMax = child.findMaxInvisible();
if (curMax && curMax.prob > maxProb) {
maxProb = curMax.prob;
maxProbNode = curMax;
}
}
return maxProbNode;
}
drawToken(ctx) {
ctx.strokeStyle = TokenNode.hovered === this ? 'black' : 'lightgray';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.roundRect(0, 0, this.tokenWidth, this.tokenHeight, this.fontSize * 0.2);
ctx.stroke();
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `bold ${this.fontSize}px JetBrains Mono, monospace`;
ctx.fillText(this.token, this.tokenWidth / 2, this.tokenHeight / 2);
if (TokenNode.hovered && TokenNode.hovered.parent === this.parent) {
ctx.strokeStyle = 'white';
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.textAlign = 'right';
ctx.fillStyle = 'orange';
const text = (this.prob * 100).toFixed(2) + '%';
ctx.fillStyle = 'black';
const labelWidth = this.fontSize * text.length * 0.6;
ctx.fillRect(-labelWidth - 5, 0, labelWidth, this.tokenHeight);
ctx.fillStyle = 'white';
ctx.fillText(text, -5, this.tokenHeight / 2);
}
}
drawLines(ctx) {
if (this.linesPadLeft < 1) return;
const initY = this.tokenHeight / 2;
let curY = initY;
ctx.strokeStyle = 'gray';
const midX = this.tokenWidth + this.linesPadLeft / 2;
this.forEachVisibleChild((child, childNum) => {
ctx.beginPath();
ctx.moveTo(this.tokenWidth, initY);
if (childNum >= 1) {
ctx.bezierCurveTo(
midX, initY,
midX, initY,
midX, this.tokenHeight,
);
ctx.lineTo(midX, curY - this.tokenHeight / 2);
ctx.bezierCurveTo(
midX, curY,
midX, curY,
this.tokenWidth + this.linesPadLeft, curY,
);
} else {
ctx.lineTo(this.tokenWidth + this.linesPadLeft, curY);
}
ctx.stroke();
curY += TokenNode.nodesVPad + child.fullHeight;
});
}
draw(ctx) {
ctx.save();
ctx.translate(this.dx, this.dy);
this.drawToken(ctx);
this.drawLines(ctx);
this.forEachVisibleChild(child => {
child.draw(ctx);
});
ctx.restore();
}
}
Insert cell
{
const int = new Interface(width);
invalidation.then(() => int.isVisible = false);
return int.canvas;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more