aione-web 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AIOne.d.ts +21 -0
- package/AIOne.js +119 -0
- package/README.md +0 -3
- package/RPCClient.js +55 -0
- package/package.json +65 -4
- package/worker-runtime.js +124 -0
- package/workerfs-bridge.js +10 -0
package/AIOne.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AIOneOptions {
|
|
2
|
+
workerUrl?: string | URL;
|
|
3
|
+
runtimeUrl?: string;
|
|
4
|
+
wasmUrl?: string;
|
|
5
|
+
threads?: boolean;
|
|
6
|
+
name?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default class AIOne {
|
|
10
|
+
static create(options?: AIOneOptions): Promise<AIOne>;
|
|
11
|
+
|
|
12
|
+
loadModel(file: File): Promise<void>;
|
|
13
|
+
generate(prompt: string, maxTokens?: number): Promise<string>;
|
|
14
|
+
unloadModel(): Promise<void>;
|
|
15
|
+
close(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Message {
|
|
19
|
+
role: "user" | "assistant";
|
|
20
|
+
content: string;
|
|
21
|
+
}
|
package/AIOne.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export default class AIOne {
|
|
2
|
+
static async create(options = {}) {
|
|
3
|
+
const workerUrl = new URL(
|
|
4
|
+
options.workerUrl ?? "/aio1-worker.js",
|
|
5
|
+
globalThis.location?.href ?? import.meta.url
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
if (options.threads) {
|
|
9
|
+
workerUrl.searchParams.set("threads", "1");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (options.runtimeUrl) {
|
|
13
|
+
workerUrl.searchParams.set("runtime", options.runtimeUrl);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (options.wasmUrl) {
|
|
17
|
+
workerUrl.searchParams.set("wasm", options.wasmUrl);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof SharedWorker === "undefined") {
|
|
21
|
+
throw new Error("SharedWorker is not supported by this browser");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const worker = new SharedWorker(workerUrl, {
|
|
25
|
+
name: options.name ?? "AIOne",
|
|
26
|
+
type: "classic"
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
worker.onerror = event => {
|
|
30
|
+
console.error("AIOne SharedWorker error:", event);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const client = new AIOne(worker.port);
|
|
34
|
+
await client.ready();
|
|
35
|
+
|
|
36
|
+
return client;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor(port) {
|
|
40
|
+
this.port = port;
|
|
41
|
+
this.nextId = 0;
|
|
42
|
+
this.pending = new Map();
|
|
43
|
+
|
|
44
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
45
|
+
this.resolveReady = resolve;
|
|
46
|
+
this.rejectReady = reject;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.port.onmessage = ({ data }) => {
|
|
50
|
+
if (data.type === "ready") {
|
|
51
|
+
this.resolveReady();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pending = this.pending.get(data.id);
|
|
56
|
+
if (!pending) return;
|
|
57
|
+
|
|
58
|
+
this.pending.delete(data.id);
|
|
59
|
+
|
|
60
|
+
if (data.error) {
|
|
61
|
+
pending.reject(new Error(data.error));
|
|
62
|
+
} else {
|
|
63
|
+
pending.resolve(data.result);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
this.port.onmessageerror = () => {
|
|
68
|
+
this.rejectReady(new Error("AIOne worker message error"));
|
|
69
|
+
|
|
70
|
+
for (const pending of this.pending.values()) {
|
|
71
|
+
pending.reject(new Error("AIOne worker message error"));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.pending.clear();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.port.start();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ready() {
|
|
81
|
+
return this.readyPromise;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
call(method, args = {}) {
|
|
85
|
+
const id = ++this.nextId;
|
|
86
|
+
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
this.pending.set(id, { resolve, reject });
|
|
89
|
+
|
|
90
|
+
this.port.postMessage({
|
|
91
|
+
id,
|
|
92
|
+
method,
|
|
93
|
+
args
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
loadModel(file) {
|
|
99
|
+
return this.call("loadModel", { file });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
generate(prompt, maxTokens = 512) {
|
|
103
|
+
return this.call("generate", { prompt, maxTokens });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
unloadModel() {
|
|
107
|
+
return this.call("unloadModel");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
close() {
|
|
111
|
+
this.port.close();
|
|
112
|
+
|
|
113
|
+
for (const pending of this.pending.values()) {
|
|
114
|
+
pending.reject(new Error("AIOne connection closed"));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.pending.clear();
|
|
118
|
+
}
|
|
119
|
+
}
|
package/README.md
CHANGED
package/RPCClient.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export default class RPCClient {
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
this.workerUrl = options.workerUrl ??
|
|
4
|
+
new URL("./shared-worker.js", import.meta.url);
|
|
5
|
+
|
|
6
|
+
this.pending = new Map();
|
|
7
|
+
this.nextId = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
connect() {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
this.worker = new SharedWorker(this.workerUrl, {
|
|
13
|
+
type: "module",
|
|
14
|
+
name: "AIOne"
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
this.port = this.worker.port;
|
|
18
|
+
|
|
19
|
+
this.port.onmessage = ({ data }) => {
|
|
20
|
+
const pending = this.pending.get(data.id);
|
|
21
|
+
if (!pending) return;
|
|
22
|
+
|
|
23
|
+
this.pending.delete(data.id);
|
|
24
|
+
|
|
25
|
+
if (data.error) {
|
|
26
|
+
pending.reject(new Error(data.error));
|
|
27
|
+
} else {
|
|
28
|
+
pending.resolve(data.result);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
this.port.onmessageerror = () => {
|
|
33
|
+
reject(new Error("AIOne worker message error"));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
this.port.start();
|
|
37
|
+
|
|
38
|
+
this.call("hello").then(resolve, reject);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
call(method, args = {}) {
|
|
43
|
+
const id = ++this.nextId;
|
|
44
|
+
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
this.pending.set(id, { resolve, reject });
|
|
47
|
+
|
|
48
|
+
this.port.postMessage({
|
|
49
|
+
id,
|
|
50
|
+
method,
|
|
51
|
+
args
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aione-web",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Browser AI inference powered by llama.cpp and WebGPU",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"llm",
|
|
8
|
+
"llama.cpp",
|
|
9
|
+
"webgpu",
|
|
10
|
+
"wasm",
|
|
11
|
+
"inference",
|
|
12
|
+
"browser",
|
|
13
|
+
"sharedworker"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/Iemand005/AIOneWeb#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/Iemand005/AIOneWeb/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Iemand005/AIOneWeb.git"
|
|
22
|
+
},
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"author": "",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./AIOne.js",
|
|
27
|
+
"module": "./AIOne.js",
|
|
28
|
+
"types": "./AIOne.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./AIOne.d.ts",
|
|
32
|
+
"import": "./AIOne.js",
|
|
33
|
+
"default": "./AIOne.js"
|
|
34
|
+
},
|
|
35
|
+
"./rpc": {
|
|
36
|
+
"import": "./RPCClient.js",
|
|
37
|
+
"default": "./RPCClient.js"
|
|
38
|
+
},
|
|
39
|
+
"./runtime": "./worker-runtime.js",
|
|
40
|
+
"./workerfs-bridge": "./workerfs-bridge.js",
|
|
41
|
+
"./single": "./build/Release/AIOneWeb.js",
|
|
42
|
+
"./single/wasm": "./build/Release/AIOneWeb.wasm",
|
|
43
|
+
"./mt": "./build/Release-mt/AIOneWebMT.js",
|
|
44
|
+
"./mt/wasm": "./build/Release-mt/AIOneWebMT.wasm",
|
|
45
|
+
"./AIOneWeb.js": "./build/Release/AIOneWeb.js",
|
|
46
|
+
"./AIOneWeb.wasm": "./build/Release/AIOneWeb.wasm",
|
|
47
|
+
"./AIOneWebMT.js": "./build/Release-mt/AIOneWebMT.js",
|
|
48
|
+
"./AIOneWebMT.wasm": "./build/Release-mt/AIOneWebMT.wasm",
|
|
49
|
+
"./build/*": "./build/*",
|
|
50
|
+
"./package.json": "./package.json"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"AIOne.js",
|
|
54
|
+
"AIOne.d.ts",
|
|
55
|
+
"RPCClient.js",
|
|
56
|
+
"worker-runtime.js",
|
|
57
|
+
"workerfs-bridge.js",
|
|
58
|
+
"build/Release/AIOneWeb.js",
|
|
59
|
+
"build/Release/AIOneWeb.wasm",
|
|
60
|
+
"build/Release-mt/AIOneWebMT.js",
|
|
61
|
+
"build/Release-mt/AIOneWebMT.wasm",
|
|
62
|
+
"README.md"
|
|
63
|
+
],
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
let module;
|
|
2
|
+
let session;
|
|
3
|
+
let initializing;
|
|
4
|
+
let mountedFile;
|
|
5
|
+
|
|
6
|
+
let operationQueue = Promise.resolve();
|
|
7
|
+
|
|
8
|
+
const WORKER_URL = new URL(self.location.href);
|
|
9
|
+
const SEARCH_PARAMS = WORKER_URL.searchParams;
|
|
10
|
+
const USE_THREADS = SEARCH_PARAMS.get("threads") === "1";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_RUNTIME_DIR = new URL(
|
|
13
|
+
USE_THREADS ? "./build/Release-mt/" : "./build/Release/",
|
|
14
|
+
WORKER_URL
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const RUNTIME_URL = SEARCH_PARAMS.get("runtime")
|
|
18
|
+
? new URL(SEARCH_PARAMS.get("runtime"), WORKER_URL).href
|
|
19
|
+
: new URL(
|
|
20
|
+
USE_THREADS ? "AIOneWebMT.js" : "AIOneWeb.js",
|
|
21
|
+
DEFAULT_RUNTIME_DIR
|
|
22
|
+
).href;
|
|
23
|
+
|
|
24
|
+
const WASM_URL = SEARCH_PARAMS.get("wasm");
|
|
25
|
+
const HAS_EXTERNAL_RUNTIME = typeof Module !== "undefined";
|
|
26
|
+
|
|
27
|
+
if (!HAS_EXTERNAL_RUNTIME) {
|
|
28
|
+
importScripts(RUNTIME_URL);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function locateFile(path) {
|
|
32
|
+
if (WASM_URL) {
|
|
33
|
+
return new URL(WASM_URL, WORKER_URL).href;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new URL(path, RUNTIME_URL).href;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function initialize(file) {
|
|
40
|
+
if (session) {
|
|
41
|
+
throw new Error("A model is already loaded");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (module && mountedFile !== file.name) {
|
|
45
|
+
throw new Error("This runtime is already initialized with another model file");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!initializing && !module) {
|
|
49
|
+
initializing = (async () => {
|
|
50
|
+
const moduleOptions = { workerFile: file };
|
|
51
|
+
|
|
52
|
+
if (WASM_URL || !HAS_EXTERNAL_RUNTIME) {
|
|
53
|
+
moduleOptions.locateFile = locateFile;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module = await Module(moduleOptions);
|
|
57
|
+
|
|
58
|
+
mountedFile = file.name;
|
|
59
|
+
session = new module.ModelSession();
|
|
60
|
+
})();
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await initializing;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
initializing = undefined;
|
|
66
|
+
module = undefined;
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
} else if (initializing) {
|
|
70
|
+
await initializing;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
session.loadModel("/models/" + file.name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function invoke(method, args) {
|
|
77
|
+
switch (method) {
|
|
78
|
+
case "loadModel":
|
|
79
|
+
return initialize(args.file);
|
|
80
|
+
|
|
81
|
+
case "generate":
|
|
82
|
+
if (!session?.isLoaded()) {
|
|
83
|
+
throw new Error("No model is loaded");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return session.generate(args.prompt, args.maxTokens ?? 512);
|
|
87
|
+
|
|
88
|
+
case "unloadModel":
|
|
89
|
+
if (session?.isLoaded()) {
|
|
90
|
+
session.unloadModel();
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
throw new Error(`Unknown method: ${method}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
self.onconnect = event => {
|
|
100
|
+
const port = event.ports[0];
|
|
101
|
+
|
|
102
|
+
port.onmessage = ({ data }) => {
|
|
103
|
+
const execute = operationQueue.then(() =>
|
|
104
|
+
invoke(data.method, data.args ?? {})
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
operationQueue = execute.catch(() => {});
|
|
108
|
+
|
|
109
|
+
execute.then(
|
|
110
|
+
result => port.postMessage({
|
|
111
|
+
id: data.id,
|
|
112
|
+
result
|
|
113
|
+
}),
|
|
114
|
+
|
|
115
|
+
error => port.postMessage({
|
|
116
|
+
id: data.id,
|
|
117
|
+
error: error?.message ?? String(error)
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
port.start();
|
|
123
|
+
port.postMessage({ type: "ready" });
|
|
124
|
+
};
|