diff --git a/ui/package-lock.json b/ui/package-lock.json index 8f3e412..66191e6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -7,14 +7,9 @@ "": { "name": "ui", "version": "0.0.1", - "dependencies": { - "@msgpack/msgpack": "^3.0.0-beta2", - "rxjs": "^7.8.1", - "wasmrs-js": "file:../../wasmrs-js", - "wick-js": "file:../../wick-js" - }, "devDependencies": { - "@bjorn3/browser_wasi_shim": "file:../../browser_wasi_shim", + "@candlecorp/wick": "^0.2.1", + "@msgpack/msgpack": "^3.0.0-beta2", "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-static": "^2.0.3", "@sveltejs/kit": "^1.20.4", @@ -33,6 +28,7 @@ "postcss-load-config": "^4.0.1", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", + "rxjs": "^7.8.1", "svelte": "^4.0.5", "svelte-check": "^3.4.3", "tailwindcss": "^3.3.2", @@ -40,13 +36,14 @@ "typescript": "^5.2.2", "vite": "^4.4.2", "vite-plugin-mkcert": "^1.16.0", - "vite-plugin-node-polyfills": "^0.15.0" + "vite-plugin-node-polyfills": "^0.15.0", + "wasmrs-js": "^0.2.4" } }, "../../browser_wasi_shim": { - "name": "@bjorn3/browser_wasi_shim", - "version": "0.2.15", - "dev": true, + "name": "@candlecorp/browser_wasi_shim", + "version": "0.2.16", + "extraneous": true, "license": "MIT OR Apache-2.0", "devDependencies": { "@playwright/test": "^1.38.1", @@ -63,55 +60,23 @@ "typescript": "^4.9.5" } }, - "../../wasmrs-js": { - "license": "Apache-2.0", - "dependencies": { - "@bjorn3/browser_wasi_shim": "file:../browser_wasi_shim", - "@candlecorp/rsocket-adapter-rxjs": "^0.0.1", - "debug": "^4.3.4", - "rsocket-core": "^1.0.0-alpha.3", - "typescript": "^5.2.2" - }, - "devDependencies": { - "@jest/globals": "^29.7.0", - "@msgpack/msgpack": "^3.0.0-beta2", - "@rollup/plugin-commonjs": "^25.0.5", - "@rollup/plugin-node-resolve": "^15.2.2", - "@rollup/plugin-typescript": "^11.1.5", - "@types/debug": "^4.1.9", - "@types/jest": "^27.0.3", - "@types/node": "^20.7.2", - "@typescript-eslint/eslint-plugin": "^6.7.3", - "@typescript-eslint/parser": "^6.7.3", - "eslint": "~8.5.0", - "eslint-plugin-import": "~2.25.3", - "eslint-plugin-prettier": "^4.0.0", - "jest": "^29.7.0", - "jest-config": "^29.7.0", - "jest-runner-eslint": "^2.1.2", - "prettier": "^2.5.1", - "rollup": "^4.0.0", - "rsocket-messaging": "^1.0.0-alpha.3", - "rxjs": "^7.8.1", - "ts-jest": "^29.1.1", - "web-worker": "^1.2.0" - } - }, "../../wick-js": { + "name": "@candlecorp/wick", + "version": "0.1.1", + "extraneous": true, "license": "Apache-2.0", "dependencies": { - "@bjorn3/browser_wasi_shim": "file:../browser_wasi_shim", "@candlecorp/rsocket-adapter-rxjs": "^0.0.1", "@msgpack/msgpack": "^3.0.0-beta2", - "@types/debug": "^4.1.9", "debug": "^4.3.4", "rsocket-core": "^1.0.0-alpha.3", "rsocket-messaging": "^1.0.0-alpha.3", "rxjs": "^7.8.1", - "wasmrs-js": "file:../wasmrs-js" + "wasmrs-js": "^0.2.3" }, "devDependencies": { "@jest/globals": "^29.7.0", + "@types/debug": "^4.1.9", "@types/jest": "^27.0.3", "@types/node": "^20.7.2", "@typescript-eslint/eslint-plugin": "^6.7.3", @@ -126,6 +91,9 @@ "ts-jest": "^29.1.1", "ts-loader": "^9.4.4", "typescript": "~4.5.4" + }, + "engines": { + "node": ">=0.19.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -162,9 +130,40 @@ "node": ">=6.0.0" } }, - "node_modules/@bjorn3/browser_wasi_shim": { - "resolved": "../../browser_wasi_shim", - "link": true + "node_modules/@candlecorp/browser_wasi_shim": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/@candlecorp/browser_wasi_shim/-/browser_wasi_shim-0.2.16.tgz", + "integrity": "sha512-aWH+TNqN0RkPbazUPyl3R6FMlTR8BlFqT7z+gYmGJyKI1TGRqpQqRXgPHTphiOumKkPmXNY5di0SE2vJ7taWpQ==", + "dev": true + }, + "node_modules/@candlecorp/rsocket-adapter-rxjs": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@candlecorp/rsocket-adapter-rxjs/-/rsocket-adapter-rxjs-0.0.1.tgz", + "integrity": "sha512-l1HU/p8flhMokEB4kkGzamSpVRstVFwQzmsWOLDwT6SAl2L+aqZuV2Id0yB04I1hpaBeg6JiYzJmnr17w2uCSA==", + "dev": true, + "dependencies": { + "rsocket-core": "^1.0.0-alpha.3", + "rsocket-messaging": "^1.0.0-alpha.3", + "rxjs": "^7.4.0" + } + }, + "node_modules/@candlecorp/wick": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@candlecorp/wick/-/wick-0.2.1.tgz", + "integrity": "sha512-z66WJDw/thYX20sBoBtqPE7ozm51aGWSFiA6NsHv3PQ9UaAXSyhfavA5BmRp7Yyjy/RXPA/Hl38Ev0BprZUm+w==", + "dev": true, + "dependencies": { + "@candlecorp/rsocket-adapter-rxjs": "^0.0.1", + "@msgpack/msgpack": "^3.0.0-beta2", + "debug": "^4.3.4", + "rsocket-core": "^1.0.0-alpha.3", + "rsocket-messaging": "^1.0.0-alpha.3", + "rxjs": "^7.8.1", + "wasmrs-js": "^0.2.4" + }, + "engines": { + "node": ">=0.19.0" + } }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", @@ -684,6 +683,7 @@ "version": "3.0.0-beta2", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz", "integrity": "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw==", + "dev": true, "engines": { "node": ">= 14" } @@ -4517,6 +4517,31 @@ "fsevents": "~2.3.2" } }, + "node_modules/rsocket-composite-metadata": { + "version": "1.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/rsocket-composite-metadata/-/rsocket-composite-metadata-1.0.0-alpha.3.tgz", + "integrity": "sha512-o+msmc1pXnejLw4TDgMllynS9/L3TGJM25kqedTxqU6wVF9jnTA95w7T4scGwwX6qiAryT90HQGUaT+An44X3w==", + "dev": true, + "dependencies": { + "rsocket-core": "^1.0.0-alpha.3" + } + }, + "node_modules/rsocket-core": { + "version": "1.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/rsocket-core/-/rsocket-core-1.0.0-alpha.3.tgz", + "integrity": "sha512-BzIe2w8dFJlUS5N9fGUNRkxL19kd64bxbXsT11wj7isLfKkPZeNXisB2p/LWvSjFzWStnpOiScZ0g3/8ROE0pw==", + "dev": true + }, + "node_modules/rsocket-messaging": { + "version": "1.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/rsocket-messaging/-/rsocket-messaging-1.0.0-alpha.3.tgz", + "integrity": "sha512-fr/BDCLI4DMyzVI5hHsABVpKlKxGX8rbfdgcXgY6VKuBlDIX0oqGApqLdSq0YfvNpgjlpuOXYj4vlhLku959Yw==", + "dev": true, + "dependencies": { + "rsocket-composite-metadata": "^1.0.0-alpha.3", + "rsocket-core": "^1.0.0-alpha.3" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4544,6 +4569,7 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -5268,7 +5294,8 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true }, "node_modules/tty-browserify": { "version": "0.0.1", @@ -5517,8 +5544,25 @@ "dev": true }, "node_modules/wasmrs-js": { - "resolved": "../../wasmrs-js", - "link": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/wasmrs-js/-/wasmrs-js-0.2.4.tgz", + "integrity": "sha512-X4rl5fUPgCb/+uvJ8Je2XOsMho0G0CDvf2IQ4rSNupPpU6RoFcERre59d943X6JNgqVwIJSRgUW7H9VRMlcZ/w==", + "dev": true, + "dependencies": { + "@candlecorp/browser_wasi_shim": "^0.2.16", + "debug": "^4.3.4", + "rsocket-core": "^1.0.0-alpha.3", + "web-worker": "^1.2.0" + }, + "engines": { + "node": ">=0.19.0" + } + }, + "node_modules/web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "dev": true }, "node_modules/webidl-conversions": { "version": "3.0.1", @@ -5570,10 +5614,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wick-js": { - "resolved": "../../wick-js", - "link": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/ui/package.json b/ui/package.json index d034698..48e0a84 100644 --- a/ui/package.json +++ b/ui/package.json @@ -12,7 +12,8 @@ "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { - "@bjorn3/browser_wasi_shim": "file:../../browser_wasi_shim", + "@candlecorp/wick": "^0.2.1", + "@msgpack/msgpack": "^3.0.0-beta2", "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-static": "^2.0.3", "@sveltejs/kit": "^1.20.4", @@ -31,6 +32,7 @@ "postcss-load-config": "^4.0.1", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", + "rxjs": "^7.8.1", "svelte": "^4.0.5", "svelte-check": "^3.4.3", "tailwindcss": "^3.3.2", @@ -38,13 +40,8 @@ "typescript": "^5.2.2", "vite": "^4.4.2", "vite-plugin-mkcert": "^1.16.0", - "vite-plugin-node-polyfills": "^0.15.0" + "vite-plugin-node-polyfills": "^0.15.0", + "wasmrs-js": "^0.2.4" }, - "type": "module", - "dependencies": { - "@msgpack/msgpack": "^3.0.0-beta2", - "rxjs": "^7.8.1", - "wasmrs-js": "file:../../wasmrs-js", - "wick-js": "file:../../wick-js" - } + "type": "module" } diff --git a/ui/src/components/BundleDownload.svelte b/ui/src/components/BundleDownload.svelte index 074cb66..dbbda74 100644 --- a/ui/src/components/BundleDownload.svelte +++ b/ui/src/components/BundleDownload.svelte @@ -36,9 +36,7 @@ async function startDownload() { downloading = true; - console.log({ selectedModel: selectedBundle }); const bundle = bundles[selectedBundle]; - console.log({ files: bundle }); const path = selectedBundle; downloadPercentage.set(Object.fromEntries(bundle.files.map((entry) => [entry.path, 0]))); for (let i = 0; i < bundle.files.length; i++) { @@ -78,15 +76,14 @@
{#each Object.entries($downloadPercentage) as [path, pct]} - - {path} - - {#if pct === 100} - - {:else} - - {/if} - + + {#if pct === 100} + + {:else if pct === undefined} + + {:else} + + {/if} {/each}
diff --git a/ui/src/components/Llama.svelte b/ui/src/components/Llama.svelte index d86a5f8..7696ae5 100644 --- a/ui/src/components/Llama.svelte +++ b/ui/src/components/Llama.svelte @@ -5,45 +5,11 @@ import { decode, encode } from '@msgpack/msgpack'; import { from } from 'rxjs'; - import { Packet, Wick, wasi } from 'wick-js'; + import { Packet } from '@candlecorp/wick'; + import { instantiateComponentWorker } from '$lib/workers'; export let bundleName: string; - async function instantiateComponent() { - const wasiOpts: wasi.WasiOptions = { - version: wasi.WasiVersions.SnapshotPreview1, - args: [], - env: { RUST_LOG: 'trace' }, - preopens: { - '/': 'opfs:/' - }, - stdin: 0, - stdout: 1, - stderr: 2 - }; - - let wasm = await (await fetch('/infer.signed.wasm')).arrayBuffer(); - - try { - const workerUrl = new URL('../lib/component-worker.ts', import.meta.url); - const component = await Wick.Component.WasmRs.FromBytes(wasm, { wasi: wasiOpts, workerUrl }); - - const config = { - config: { - model_dir: '/', - model: `${bundleName}.bin`, - tokenizer: 'tokenizer.json' - } - }; - console.log({ config }); - - const instance = await component.instantiate(config); - return instance; - } catch (e) { - console.error(`Error instantiating component: ${e}`); - } - } - let message = 'Once upon a time, in a land far away...'; let chatHistory: Writable> = writable([]); let isResponding = false; @@ -70,11 +36,11 @@ } async function getAIResponse(input: string, aiMessageIndex: number): Promise { - const inst = await instantiateComponent(); - if (!inst) { - console.log('no instance running'); - return; - } + const inst = await instantiateComponentWorker('/infer.signed.wasm', { + model_dir: '/', + model: `${bundleName}.bin`, + tokenizer: 'tokenizer.json' + }); const stream = from([new Packet('prompt', encode(input)), Packet.Done('prompt')]); const result = await inst.invoke('generate', stream, { max_length: 512 }); diff --git a/ui/src/lib/component-worker.ts b/ui/src/lib/component-worker.js similarity index 98% rename from ui/src/lib/component-worker.ts rename to ui/src/lib/component-worker.js index dedb557..9888f72 100644 --- a/ui/src/lib/component-worker.ts +++ b/ui/src/lib/component-worker.js @@ -1,3 +1,2 @@ import { worker } from 'wasmrs-js'; - worker.main(); diff --git a/ui/src/lib/fs/types.ts b/ui/src/lib/fs/types.ts index 2040b62..a6b15a4 100644 --- a/ui/src/lib/fs/types.ts +++ b/ui/src/lib/fs/types.ts @@ -24,7 +24,7 @@ export type WorkerMessage = ProgressMessage | CompleteMessage | ErrorMessage; export interface ProgressMessage { type: WorkerMessageType.Progress; - percentage: number; + percentage: number | undefined; path: string; } diff --git a/ui/src/lib/fs/worker.ts b/ui/src/lib/fs/worker.ts index ae152b7..15f561e 100644 --- a/ui/src/lib/fs/worker.ts +++ b/ui/src/lib/fs/worker.ts @@ -22,9 +22,8 @@ self.onmessage = async (e) => { if (!response.ok) throw new Error('Network response was not ok ' + response.statusText); const contentLength = response.headers.get('Content-Length'); + const totalSize = contentLength ? parseInt(contentLength, 10) : undefined; - if (totalSize === undefined || isNaN(totalSize)) - throw new Error('Content-Length header is missing or invalid'); const segments = path.split('/'); const filename = segments[segments.length - 1]; @@ -51,23 +50,36 @@ self.onmessage = async (e) => { sendMessage({ type: WorkerMessageType.Progress, path, percentage: 100 }); } else { const accessHandle = await fileHandle.createSyncAccessHandle(); - accessHandle.truncate(totalSize); - const reader = response.body.getReader(); - let receivedSize = 0; - let position = 0; - // eslint-disable-next-line no-constant-condition - while (true) { - const { done, value } = await reader.read(); - if (done) break; + try { + accessHandle.truncate(0); + const reader = response.body.getReader(); - await accessHandle.write(value, { at: position }); - position += value.length; - receivedSize += value.length; + let receivedSize = 0; + let position = 0; + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader.read(); + if (done) { + sendMessage({ type: WorkerMessageType.Progress, path, percentage: 100 }); + break; + } - const percentage = Math.round((receivedSize / totalSize) * 100); - sendMessage({ type: WorkerMessageType.Progress, path, percentage }); - } + await accessHandle.write(value, { at: position }); + position += value.length; + receivedSize += value.length; + if (totalSize === undefined || isNaN(totalSize)) { + sendMessage({ type: WorkerMessageType.Progress, path, percentage: undefined }); + } else { + const percentage = Math.round((receivedSize / totalSize) * 100); + sendMessage({ type: WorkerMessageType.Progress, path, percentage }); + } + } + } catch (e) { + await accessHandle.flush(); + accessHandle.close(); + throw e; + } await accessHandle.flush(); accessHandle.close(); } diff --git a/ui/src/lib/workers.ts b/ui/src/lib/workers.ts index 2ba1547..8fd41bc 100644 --- a/ui/src/lib/workers.ts +++ b/ui/src/lib/workers.ts @@ -1,3 +1,9 @@ +import { WasmRsComponentInstance, Wick } from '@candlecorp/wick'; +import { wasi } from 'wasmrs-js'; +import ComponentWorker from './component-worker.js?worker&url'; + +import { dev } from '$app/environment'; + const workers = new Map(); export enum WorkerKind { @@ -13,13 +19,50 @@ export function loadWorker(id: string, kind: WorkerKind) { case WorkerKind.Fs: worker = new Worker(new URL(`$lib/fs/worker.js`, import.meta.url), { type: 'module' }); break; - case WorkerKind.Component: - worker = new Worker(new URL(`$lib/component/worker.js`, import.meta.url), { type: 'module' }); - break; - default: throw new Error(`Unknown worker: ${id}`); } workers.set(id, worker); return worker; } + +export async function instantiateComponentWorker( + wasmUrl: string, + componentConfig: unknown +): Promise { + const wasiOpts: wasi.WasiOptions = { + version: wasi.WasiVersions.SnapshotPreview1, + args: [], + env: { RUST_LOG: 'trace' }, + preopens: { + '/': 'opfs:/' + }, + stdin: 0, + stdout: 1, + stderr: 2 + }; + + // const wasm = await (await fetch(wasmUrl)).arrayBuffer(); + const wasm = await fetch(wasmUrl); + + try { + let workerUrl: URL; + if (dev) { + workerUrl = new URL('$lib/component-worker.js', import.meta.url); + } else { + workerUrl = new URL(ComponentWorker, import.meta.url); + } + + const component = await Wick.Component.WasmRs.FromResponse(wasm, { wasi: wasiOpts, workerUrl }); + // const component = await Wick.Component.WasmRs.FromBytes(wasm, { wasi: wasiOpts, workerUrl }); + const config = { + config: componentConfig + }; + + const instance = await component.instantiate(config); + return instance; + } catch (e) { + console.error(`Error instantiating component`, e); + throw e; + } +} diff --git a/ui/src/routes/redact/+page.svelte b/ui/src/routes/redact/+page.svelte index 565cbeb..ad2ae05 100644 --- a/ui/src/routes/redact/+page.svelte +++ b/ui/src/routes/redact/+page.svelte @@ -2,36 +2,8 @@ import { decode, encode } from '@msgpack/msgpack'; import { Button, Textarea } from 'flowbite-svelte'; import { from } from 'rxjs'; - import { Packet, Wick, wasi } from 'wick-js'; - - async function instantiateComponent() { - const wasiOpts: wasi.WasiOptions = { - version: wasi.WasiVersions.SnapshotPreview1, - args: [], - stdin: 0, - stdout: 1, - stderr: 2 - }; - - let wasm = await (await fetch('/redact.signed.wasm')).arrayBuffer(); - - try { - const workerUrl = new URL('../../lib/component-worker.ts', import.meta.url); - const component = await Wick.Component.WasmRs.FromBytes(wasm, { wasi: wasiOpts, workerUrl }); - - const ctx = { - config: {} - }; - - const instance = await component.instantiate(ctx); - return instance; - } catch (e) { - console.error(`Error instantiating component: ${e}`); - } - } - - let input = ''; - let validated = ''; + import { Packet } from '@candlecorp/wick'; + import { instantiateComponentWorker } from '$lib/workers'; let inputObj = { name: 'John', @@ -43,12 +15,11 @@ let sampleInput = JSON.stringify(inputObj, null, 4); + let input = sampleInput; + let validated = ''; + async function validateInput(e: MouseEvent): Promise { - const inst = await instantiateComponent(); - if (!inst) { - console.log('no instance running'); - return; - } + const inst = await instantiateComponentWorker('/redact.signed.wasm', {}); const stream = from([new Packet('input', encode(sampleInput)), Packet.Done('input')]); const result = await inst.invoke('regex', stream, { diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 5c31ebc..863ca31 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -4,10 +4,15 @@ import { nodePolyfills } from 'vite-plugin-node-polyfills'; import mkcert from 'vite-plugin-mkcert'; export default defineConfig({ + optimizeDeps: { + include: ['debug', 'rsocket-core', 'web-worker'] + }, + resolve: { alias: { 'wasmrs-js': './node_modules/wasmrs-js/dist/src/browser/index.js' } }, + plugins: [mkcert(), nodePolyfills(), sveltekit()] });