1
0
Fork 0

Compare commits

..

2 Commits

Author SHA1 Message Date
Pabloader f25621daef Sand sim 2026-03-03 13:11:43 +00:00
Pabloader 4d2b1e26f7 fix minichat 2026-02-24 07:17:44 +00:00
7 changed files with 188 additions and 19 deletions

View File

@ -10,13 +10,14 @@
"@huggingface/jinja": "0.5.5",
"@huggingface/tokenizers": "0.1.1",
"@inquirer/select": "2.3.10",
"@types/node": "^25.3.3",
"ace-builds": "1.36.3",
"clsx": "2.1.1",
"delay": "6.0.0",
"preact": "10.22.0",
},
"devDependencies": {
"@types/bun": "latest",
"@types/bun": "^1.3.10",
"@types/html-minifier": "4.0.5",
"@types/inquirer": "9.0.7",
"@types/web-bluetooth": "0.0.21",
@ -47,7 +48,7 @@
"@inquirer/type": ["@inquirer/type@1.4.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-AjOqykVyjdJQvtfkNDGUyMYGF8xN50VUxftCQWsOyIo4DFRLr6VQhW0VItGI1JIyQGCGgIpKa7hMMwNhZb4OIw=="],
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@types/clean-css": ["@types/clean-css@4.2.11", "", { "dependencies": { "@types/node": "*", "source-map": "^0.6.0" } }, "sha512-Y8n81lQVTAfP2TOdtJJEsCoYl1AnOkqDqMvXb9/7pfgZZ7r8YrEyurrAvAoAjHOGXKRybay+5CsExqIH6liccw=="],
@ -57,9 +58,7 @@
"@types/mute-stream": ["@types/mute-stream@0.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow=="],
"@types/node": ["@types/node@20.14.10", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="],
"@types/relateurl": ["@types/relateurl@0.2.33", "", {}, "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw=="],
@ -81,7 +80,7 @@
"browser-detect": ["browser-detect@0.2.28", "", { "dependencies": { "core-js": "^2.5.7" } }, "sha512-KeWGHqYQmHDkCFG2dIiX/2wFUgqevbw/rd6wNi9N6rZbaSJFtG5kel0HtprRwCGp8sqpQP79LzDJXf/WCx4WAw=="],
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"camel-case": ["camel-case@3.0.0", "", { "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.1.1" } }, "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w=="],
@ -103,8 +102,6 @@
"core-js": ["core-js@2.6.12", "", {}, "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"delay": ["delay@6.0.0", "", {}, "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@ -147,7 +144,7 @@
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"upper-case": ["upper-case@1.1.3", "", {}, "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA=="],
@ -155,10 +152,26 @@
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="],
"@inquirer/core/@types/node": ["@types/node@20.14.10", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="],
"@types/clean-css/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
"@types/mute-stream/@types/node": ["@types/node@20.14.10", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="],
"@types/through/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
"bun-types/@types/node": ["@types/node@20.14.10", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ=="],
"html-minifier/uglify-js": ["uglify-js@3.18.0", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A=="],
"@inquirer/core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@types/clean-css/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@types/mute-stream/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@types/through/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
}
}

View File

@ -13,13 +13,14 @@
"@huggingface/jinja": "0.5.5",
"@huggingface/tokenizers": "0.1.1",
"@inquirer/select": "2.3.10",
"@types/node": "^25.3.3",
"ace-builds": "1.36.3",
"clsx": "2.1.1",
"delay": "6.0.0",
"preact": "10.22.0"
},
"devDependencies": {
"@types/bun": "latest",
"@types/bun": "^1.3.10",
"@types/html-minifier": "4.0.5",
"@types/inquirer": "9.0.7",
"@types/web-bluetooth": "0.0.21",

View File

@ -3,7 +3,7 @@ export function loadImageData(dataView: DataView, pointer: number) {
const height = dataView.getUint16(pointer + 2, true);
const dataPtr = dataView.getUint32(pointer + 4, true);
const imageBuffer = new Uint8ClampedArray(dataView.buffer, dataPtr, width * height * 4);
const imageBuffer = new Uint8Array(dataView.buffer, dataPtr, width * height * 4);
const imageData = new ImageData(imageBuffer, width, height);
return imageData;
@ -66,3 +66,40 @@ export function getImageData(image: HTMLImageElement): ImageData {
return imageData;
}
export const getPixel = (imageData: ImageData, x: number, y: number): [number, number, number, number] => {
const index = (y * imageData.width + x) * 4;
return [
imageData.data[index + 0],
imageData.data[index + 1],
imageData.data[index + 2],
imageData.data[index + 3],
];
}
export const getPixelInt = (imageData: ImageData, x: number, y: number): number => {
const index = (y * imageData.width + x) * 4;
return (
imageData.data[index + 0] << 24 |
imageData.data[index + 1] << 16 |
imageData.data[index + 2] << 8 |
imageData.data[index + 3] << 0
);
}
export const setPixel = (imageData: ImageData, x: number, y: number, color: [number, number, number, number] | number) => {
const index = (y * imageData.width + x) * 4;
if (Array.isArray(color)) {
imageData.data[index + 0] = color[0];
imageData.data[index + 1] = color[1];
imageData.data[index + 2] = color[2];
imageData.data[index + 3] = color[3];
} else {
const index = (y * imageData.width + x) * 4;
imageData.data[index + 0] = (color >> 24) & 0xFF;
imageData.data[index + 1] = (color >> 16) & 0xFF;
imageData.data[index + 2] = (color >> 8) & 0xFF;
imageData.data[index + 3] = color & 0xFF;
}
}

View File

@ -2,13 +2,13 @@ import { formatError, formatErrorMessage } from "./errors";
import Input from "./input";
import { nextFrame } from "./utils";
type Setup<T extends Record<string, unknown> | void> = () => Promise<T> | T;
type Frame<T extends Record<string, unknown> | void> = (dt: number, state: T) => Promise<T | void> | T | void;
type Setup<T> = () => Promise<T> | T;
type Frame<T> = (dt: number, state: T) => Promise<T | void> | T | void;
type GameMain = () => void;
export function gameLoop<T extends Record<string, unknown> | void>(frame: Frame<T>): GameMain;
export function gameLoop<T extends Record<string, unknown> | void>(setup: Setup<T>, frame: Frame<T>): GameMain;
export function gameLoop<T extends Record<string, unknown> | void>(setupOrFrame: Setup<T> | Frame<T>, frame?: Frame<T>): GameMain {
export function gameLoop<T>(frame: Frame<T>): GameMain;
export function gameLoop<T>(setup: Setup<T>, frame: Frame<T>): GameMain;
export function gameLoop<T>(setupOrFrame: Setup<T> | Frame<T>, frame?: Frame<T>): GameMain {
return async () => {
let state: T;
try {

View File

@ -60,8 +60,14 @@ export const MiniChat = ({ history = [], buttons = {}, open, onClose }: IProps)
generating.setTrue();
for await (const chunk of generate(prompt)) {
text += chunk;
setMessages(MessageTools.updateSwipe(newMessages, messageId, { content: text.trim() }));
text += chunk.text;
setMessages(MessageTools.updateSwipe(
newMessages,
messageId,
{
content: text.trim(),
cost: chunk.cost,
}));
}
generating.setFalse();

View File

@ -251,7 +251,9 @@ export namespace Connection {
}
}
} catch (e) {
console.error('Error in horde generation:', e);
if (!signal.aborted) {
console.error('Error in horde generation:', e);
}
return yield deleteRequest();
}
}

View File

@ -0,0 +1,110 @@
import { createCanvas, getPixelInt, setPixel } from '@common/display/canvas';
import { gameLoop } from '@common/game';
import Input from '@common/input';
const sandWidth = 128;
const setup = () => {
const canvas = createCanvas(window.innerWidth >> 2, window.innerHeight >> 2);
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Failed to get canvas context');
}
document.body.style.backgroundColor = '#eee';
const imageData = initData();
return {
canvas,
ctx,
imageData,
};
};
const initData = () => {
const imageData = new ImageData(sandWidth, sandWidth);
for (let x = 0; x < imageData.width; x++) {
for (let y = 0; y < imageData.height; y++) {
const index = (y * imageData.width + x) * 4;
imageData.data[index + 0] = Math.random() < 0.3 ? 255 : 0;
imageData.data[index + 1] = Math.random() < 0.3 ? 255 : 0;
imageData.data[index + 2] = Math.random() < 0.3 ? 255 : 0;
imageData.data[index + 3] = 255;
}
}
return imageData;
}
const emptyColor = 0x000000FF;
const isEmpty = (pixel: number) => (pixel >> 8) === 0;
type State = ReturnType<typeof setup>;
const frame = async (dt: number, state: State): Promise<State> => {
const { canvas, ctx } = state;
if (Input.isPressed(Input.KeyCode.SPACE)) {
state.imageData = initData();
}
const imageData = state.imageData;
for (let y = imageData.height - 2; y >= 0; y--) {
const direction = Math.random() < 0.5 ? 1 : -1;
let startX, endX;
if (direction > 0) {
startX = 0;
endX = imageData.width;
} else {
startX = imageData.width - 1;
endX = -1;
}
for (let x = startX; x != endX; x += direction) {
const pixel = getPixelInt(imageData, x, y);
if (isEmpty(pixel)) continue;
const pixelBelow = getPixelInt(imageData, x, y + 1);
if (isEmpty(pixelBelow)) {
setPixel(imageData, x, y, emptyColor);
setPixel(imageData, x, y + 1, pixel);
continue;
}
let leftEmpty = false;
let rightEmpty = false;
if (x > 0) {
const pixelLeft = getPixelInt(imageData, x - 1, y + 1);
leftEmpty = isEmpty(pixelLeft);
}
if (x < imageData.width - 1) {
const pixelRight = getPixelInt(imageData, x + 1, y + 1);
rightEmpty = isEmpty(pixelRight);
}
const selector = Math.random() < 0.5;
const selectedLeft = (leftEmpty && rightEmpty && selector) || (leftEmpty && !rightEmpty);
const selectedRight = (leftEmpty && rightEmpty && !selector) || (!leftEmpty && rightEmpty);
if (selectedLeft) {
setPixel(imageData, x, y, emptyColor);
setPixel(imageData, x - 1, y + 1, pixel);
} else if (selectedRight) {
setPixel(imageData, x, y, emptyColor);
setPixel(imageData, x + 1, y + 1, pixel);
}
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, (canvas.width - sandWidth) / 2, (canvas.height - sandWidth) / 2);
return {
...state,
};
};
export default gameLoop(setup, frame);