wllama-service 2.0.5 → 2.0.6
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/dist/index.d.mts +21 -25
- package/dist/index.d.ts +21 -25
- package/dist/index.js +107 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +107 -84
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -13,6 +13,8 @@ interface GenerateRequest {
|
|
|
13
13
|
temperature?: number;
|
|
14
14
|
topK?: number;
|
|
15
15
|
topP?: number;
|
|
16
|
+
stop?: string | string[];
|
|
17
|
+
abortSignal?: AbortSignal;
|
|
16
18
|
}
|
|
17
19
|
interface GenerateResult {
|
|
18
20
|
success: boolean;
|
|
@@ -35,44 +37,38 @@ interface ConnectionResult {
|
|
|
35
37
|
declare class WllamaService {
|
|
36
38
|
private wllama;
|
|
37
39
|
private modelName;
|
|
38
|
-
private config;
|
|
40
|
+
private readonly config;
|
|
39
41
|
constructor(config?: WllamaServiceConfig);
|
|
40
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Check WebGPU without directly accessing navigator.gpu.
|
|
44
|
+
*
|
|
45
|
+
* This avoids requiring @webgpu/types merely for feature detection.
|
|
46
|
+
*/
|
|
47
|
+
private hasWebGPU;
|
|
48
|
+
/** Check browser compatibility and capabilities. */
|
|
41
49
|
checkEnvironment(): ConnectionResult;
|
|
42
|
-
/** Load a GGUF model file from the user's filesystem */
|
|
50
|
+
/** Load a GGUF model file from the user's filesystem. */
|
|
43
51
|
loadModel(file: File, onProgress?: (progress: number) => void): Promise<LoadModelResult>;
|
|
44
52
|
/**
|
|
45
|
-
* Generate a response using the model's chat template.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* (ChatML, Llama-3, etc.) before running inference.
|
|
53
|
+
* Generate a chat response using the model's embedded chat template.
|
|
54
|
+
*
|
|
55
|
+
* Suitable for instruction-tuned models such as Phi-3 Instruct,
|
|
56
|
+
* Llama Instruct, ChatML models, and similar chat models.
|
|
50
57
|
*/
|
|
51
58
|
generate(req: GenerateRequest): Promise<GenerateResult>;
|
|
52
59
|
/**
|
|
53
|
-
* Generate a raw completion
|
|
54
|
-
* Use this when you need to control the exact prompt string yourself —
|
|
55
|
-
* e.g. few-shot prompts, base-model completion, custom templates, or
|
|
56
|
-
* fill-in-the-middle style prompts.
|
|
60
|
+
* Generate a raw completion without applying a chat template.
|
|
57
61
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* NOT accept a `messages` array and does NOT apply any chat template —
|
|
61
|
-
* whatever string you pass is exactly what the model sees. `req.system`
|
|
62
|
-
* (if provided) is prepended as plain text; it is your responsibility to
|
|
63
|
-
* format it however the target model expects (call
|
|
64
|
-
* `this.wllama.getChatTemplate()` if you want to match the model's
|
|
65
|
-
* native template manually).
|
|
62
|
+
* Suitable for base completion models, manually formatted prompts,
|
|
63
|
+
* few-shot completion, fill-in-the-middle, and custom templates.
|
|
66
64
|
*/
|
|
67
65
|
generateCompletion(req: GenerateRequest): Promise<GenerateResult>;
|
|
68
|
-
/** Unload the current model and
|
|
66
|
+
/** Unload the current model and release its resources. */
|
|
69
67
|
unload(): Promise<void>;
|
|
70
|
-
/** Whether a model is currently loaded */
|
|
68
|
+
/** Whether a model is currently loaded. */
|
|
71
69
|
get isLoaded(): boolean;
|
|
72
|
-
/** Name of the currently loaded model */
|
|
70
|
+
/** Name of the currently loaded model. */
|
|
73
71
|
get currentModel(): string;
|
|
74
|
-
private storeInCache;
|
|
75
|
-
private deleteFromCache;
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
export { type ConnectionResult, type GenerateRequest, type GenerateResult, type LoadModelResult, WllamaService, type WllamaServiceConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ interface GenerateRequest {
|
|
|
13
13
|
temperature?: number;
|
|
14
14
|
topK?: number;
|
|
15
15
|
topP?: number;
|
|
16
|
+
stop?: string | string[];
|
|
17
|
+
abortSignal?: AbortSignal;
|
|
16
18
|
}
|
|
17
19
|
interface GenerateResult {
|
|
18
20
|
success: boolean;
|
|
@@ -35,44 +37,38 @@ interface ConnectionResult {
|
|
|
35
37
|
declare class WllamaService {
|
|
36
38
|
private wllama;
|
|
37
39
|
private modelName;
|
|
38
|
-
private config;
|
|
40
|
+
private readonly config;
|
|
39
41
|
constructor(config?: WllamaServiceConfig);
|
|
40
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Check WebGPU without directly accessing navigator.gpu.
|
|
44
|
+
*
|
|
45
|
+
* This avoids requiring @webgpu/types merely for feature detection.
|
|
46
|
+
*/
|
|
47
|
+
private hasWebGPU;
|
|
48
|
+
/** Check browser compatibility and capabilities. */
|
|
41
49
|
checkEnvironment(): ConnectionResult;
|
|
42
|
-
/** Load a GGUF model file from the user's filesystem */
|
|
50
|
+
/** Load a GGUF model file from the user's filesystem. */
|
|
43
51
|
loadModel(file: File, onProgress?: (progress: number) => void): Promise<LoadModelResult>;
|
|
44
52
|
/**
|
|
45
|
-
* Generate a response using the model's chat template.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* (ChatML, Llama-3, etc.) before running inference.
|
|
53
|
+
* Generate a chat response using the model's embedded chat template.
|
|
54
|
+
*
|
|
55
|
+
* Suitable for instruction-tuned models such as Phi-3 Instruct,
|
|
56
|
+
* Llama Instruct, ChatML models, and similar chat models.
|
|
50
57
|
*/
|
|
51
58
|
generate(req: GenerateRequest): Promise<GenerateResult>;
|
|
52
59
|
/**
|
|
53
|
-
* Generate a raw completion
|
|
54
|
-
* Use this when you need to control the exact prompt string yourself —
|
|
55
|
-
* e.g. few-shot prompts, base-model completion, custom templates, or
|
|
56
|
-
* fill-in-the-middle style prompts.
|
|
60
|
+
* Generate a raw completion without applying a chat template.
|
|
57
61
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* NOT accept a `messages` array and does NOT apply any chat template —
|
|
61
|
-
* whatever string you pass is exactly what the model sees. `req.system`
|
|
62
|
-
* (if provided) is prepended as plain text; it is your responsibility to
|
|
63
|
-
* format it however the target model expects (call
|
|
64
|
-
* `this.wllama.getChatTemplate()` if you want to match the model's
|
|
65
|
-
* native template manually).
|
|
62
|
+
* Suitable for base completion models, manually formatted prompts,
|
|
63
|
+
* few-shot completion, fill-in-the-middle, and custom templates.
|
|
66
64
|
*/
|
|
67
65
|
generateCompletion(req: GenerateRequest): Promise<GenerateResult>;
|
|
68
|
-
/** Unload the current model and
|
|
66
|
+
/** Unload the current model and release its resources. */
|
|
69
67
|
unload(): Promise<void>;
|
|
70
|
-
/** Whether a model is currently loaded */
|
|
68
|
+
/** Whether a model is currently loaded. */
|
|
71
69
|
get isLoaded(): boolean;
|
|
72
|
-
/** Name of the currently loaded model */
|
|
70
|
+
/** Name of the currently loaded model. */
|
|
73
71
|
get currentModel(): string;
|
|
74
|
-
private storeInCache;
|
|
75
|
-
private deleteFromCache;
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
export { type ConnectionResult, type GenerateRequest, type GenerateResult, type LoadModelResult, WllamaService, type WllamaServiceConfig };
|
package/dist/index.js
CHANGED
|
@@ -36,104 +36,136 @@ var WllamaService = class {
|
|
|
36
36
|
nCtx: config.nCtx ?? 2048
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Check WebGPU without directly accessing navigator.gpu.
|
|
41
|
+
*
|
|
42
|
+
* This avoids requiring @webgpu/types merely for feature detection.
|
|
43
|
+
*/
|
|
44
|
+
hasWebGPU() {
|
|
45
|
+
return typeof navigator !== "undefined" && "gpu" in navigator;
|
|
46
|
+
}
|
|
47
|
+
/** Check browser compatibility and capabilities. */
|
|
40
48
|
checkEnvironment() {
|
|
41
|
-
if (typeof
|
|
42
|
-
return {
|
|
49
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: "WllamaService can only run in a browser environment."
|
|
53
|
+
};
|
|
43
54
|
}
|
|
44
|
-
if (
|
|
45
|
-
return {
|
|
55
|
+
if (typeof WebAssembly === "undefined") {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: "WebAssembly is not supported in this browser."
|
|
59
|
+
};
|
|
46
60
|
}
|
|
47
|
-
const hasWebGPU = !!navigator.gpu;
|
|
48
|
-
const isolated = window.crossOriginIsolated;
|
|
49
61
|
return {
|
|
50
62
|
success: true,
|
|
51
|
-
hasWebGPU,
|
|
52
|
-
crossOriginIsolated:
|
|
63
|
+
hasWebGPU: this.hasWebGPU(),
|
|
64
|
+
crossOriginIsolated: window.crossOriginIsolated
|
|
53
65
|
};
|
|
54
66
|
}
|
|
55
|
-
/** Load a GGUF model file from the user's filesystem */
|
|
67
|
+
/** Load a GGUF model file from the user's filesystem. */
|
|
56
68
|
async loadModel(file, onProgress) {
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
69
|
+
const environment = this.checkEnvironment();
|
|
70
|
+
if (!environment.success) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: environment.error
|
|
74
|
+
};
|
|
75
|
+
}
|
|
59
76
|
await this.unload();
|
|
60
77
|
try {
|
|
61
78
|
onProgress?.(10);
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
this.wllama = new import_esm.Wllama({
|
|
80
|
+
default: this.config.wasmPath
|
|
81
|
+
});
|
|
64
82
|
onProgress?.(30);
|
|
65
|
-
|
|
66
|
-
onProgress?.(50);
|
|
67
|
-
const hasWebGPU = !!navigator.gpu;
|
|
83
|
+
const useWebGPU = this.hasWebGPU() && this.config.nGpuLayers > 0;
|
|
68
84
|
await this.wllama.loadModel([file], {
|
|
69
85
|
n_ctx: this.config.nCtx,
|
|
70
|
-
|
|
86
|
+
n_gpu_layers: useWebGPU ? this.config.nGpuLayers : 0,
|
|
87
|
+
jinja: true
|
|
71
88
|
});
|
|
72
89
|
this.modelName = file.name;
|
|
73
90
|
onProgress?.(100);
|
|
74
|
-
return {
|
|
75
|
-
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
usedWebGPU: useWebGPU
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
76
96
|
await this.unload();
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: error instanceof Error ? error.message : "Failed to load model"
|
|
100
|
+
};
|
|
79
101
|
}
|
|
80
102
|
}
|
|
81
103
|
/**
|
|
82
|
-
* Generate a response using the model's chat template.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* (ChatML, Llama-3, etc.) before running inference.
|
|
104
|
+
* Generate a chat response using the model's embedded chat template.
|
|
105
|
+
*
|
|
106
|
+
* Suitable for instruction-tuned models such as Phi-3 Instruct,
|
|
107
|
+
* Llama Instruct, ChatML models, and similar chat models.
|
|
87
108
|
*/
|
|
88
109
|
async generate(req) {
|
|
89
110
|
if (!this.wllama) {
|
|
90
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: "No model loaded. Call loadModel() first."
|
|
114
|
+
};
|
|
91
115
|
}
|
|
92
116
|
try {
|
|
93
|
-
const
|
|
117
|
+
const startedAt = performance.now();
|
|
94
118
|
const messages = [];
|
|
95
|
-
if (req.system)
|
|
96
|
-
|
|
119
|
+
if (req.system) {
|
|
120
|
+
messages.push({
|
|
121
|
+
role: "system",
|
|
122
|
+
content: req.system
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
messages.push({
|
|
126
|
+
role: "user",
|
|
127
|
+
content: req.prompt
|
|
128
|
+
});
|
|
97
129
|
const response = await this.wllama.createChatCompletion({
|
|
98
130
|
messages,
|
|
99
131
|
stream: false,
|
|
100
132
|
max_tokens: req.maxTokens ?? 512,
|
|
101
133
|
temperature: req.temperature ?? 0.7,
|
|
102
134
|
top_k: req.topK ?? 40,
|
|
103
|
-
top_p: req.topP ?? 0.95
|
|
135
|
+
top_p: req.topP ?? 0.95,
|
|
136
|
+
stop: req.stop,
|
|
137
|
+
abortSignal: req.abortSignal
|
|
104
138
|
});
|
|
105
|
-
const text = response
|
|
139
|
+
const text = response.choices?.[0]?.message?.content ?? "";
|
|
106
140
|
return {
|
|
107
141
|
success: true,
|
|
108
142
|
text,
|
|
109
|
-
timeMs: Math.round(
|
|
143
|
+
timeMs: Math.round(
|
|
144
|
+
performance.now() - startedAt
|
|
145
|
+
)
|
|
146
|
+
};
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: error instanceof Error ? error.message : "Generation failed"
|
|
110
151
|
};
|
|
111
|
-
} catch (e) {
|
|
112
|
-
const message = e instanceof Error ? e.message : "Generation failed";
|
|
113
|
-
return { success: false, error: message };
|
|
114
152
|
}
|
|
115
153
|
}
|
|
116
154
|
/**
|
|
117
|
-
* Generate a raw completion
|
|
118
|
-
* Use this when you need to control the exact prompt string yourself —
|
|
119
|
-
* e.g. few-shot prompts, base-model completion, custom templates, or
|
|
120
|
-
* fill-in-the-middle style prompts.
|
|
155
|
+
* Generate a raw completion without applying a chat template.
|
|
121
156
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* NOT accept a `messages` array and does NOT apply any chat template —
|
|
125
|
-
* whatever string you pass is exactly what the model sees. `req.system`
|
|
126
|
-
* (if provided) is prepended as plain text; it is your responsibility to
|
|
127
|
-
* format it however the target model expects (call
|
|
128
|
-
* `this.wllama.getChatTemplate()` if you want to match the model's
|
|
129
|
-
* native template manually).
|
|
157
|
+
* Suitable for base completion models, manually formatted prompts,
|
|
158
|
+
* few-shot completion, fill-in-the-middle, and custom templates.
|
|
130
159
|
*/
|
|
131
160
|
async generateCompletion(req) {
|
|
132
161
|
if (!this.wllama) {
|
|
133
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
error: "No model loaded. Call loadModel() first."
|
|
165
|
+
};
|
|
134
166
|
}
|
|
135
167
|
try {
|
|
136
|
-
const
|
|
168
|
+
const startedAt = performance.now();
|
|
137
169
|
const prompt = req.system ? `${req.system}
|
|
138
170
|
|
|
139
171
|
${req.prompt}` : req.prompt;
|
|
@@ -143,59 +175,50 @@ ${req.prompt}` : req.prompt;
|
|
|
143
175
|
max_tokens: req.maxTokens ?? 512,
|
|
144
176
|
temperature: req.temperature ?? 0.7,
|
|
145
177
|
top_k: req.topK ?? 40,
|
|
146
|
-
top_p: req.topP ?? 0.95
|
|
178
|
+
top_p: req.topP ?? 0.95,
|
|
179
|
+
stop: req.stop,
|
|
180
|
+
abortSignal: req.abortSignal
|
|
181
|
+
});
|
|
182
|
+
const choice = response.choices?.[0];
|
|
183
|
+
console.debug("[wllama] raw completion", {
|
|
184
|
+
text: choice?.text,
|
|
185
|
+
finishReason: choice?.finish_reason,
|
|
186
|
+
usage: response.usage,
|
|
187
|
+
timings: response.timings
|
|
147
188
|
});
|
|
148
|
-
const text = response?.choices?.[0]?.text ?? "";
|
|
149
189
|
return {
|
|
150
190
|
success: true,
|
|
151
|
-
text,
|
|
152
|
-
timeMs: Math.round(
|
|
191
|
+
text: choice?.text ?? "",
|
|
192
|
+
timeMs: Math.round(
|
|
193
|
+
performance.now() - startedAt
|
|
194
|
+
)
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: error instanceof Error ? error.message : "Generation failed"
|
|
153
200
|
};
|
|
154
|
-
} catch (e) {
|
|
155
|
-
const message = e instanceof Error ? e.message : "Generation failed";
|
|
156
|
-
return { success: false, error: message };
|
|
157
201
|
}
|
|
158
202
|
}
|
|
159
|
-
/** Unload the current model and
|
|
203
|
+
/** Unload the current model and release its resources. */
|
|
160
204
|
async unload() {
|
|
161
205
|
if (this.wllama) {
|
|
162
206
|
try {
|
|
163
207
|
await this.wllama.exit();
|
|
164
|
-
} catch
|
|
208
|
+
} catch {
|
|
165
209
|
}
|
|
166
210
|
this.wllama = null;
|
|
167
211
|
}
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
await this.deleteFromCache(this.modelName);
|
|
171
|
-
} catch (_) {
|
|
172
|
-
}
|
|
173
|
-
this.modelName = "";
|
|
174
|
-
}
|
|
212
|
+
this.modelName = "";
|
|
175
213
|
}
|
|
176
|
-
/** Whether a model is currently loaded */
|
|
214
|
+
/** Whether a model is currently loaded. */
|
|
177
215
|
get isLoaded() {
|
|
178
216
|
return this.wllama !== null;
|
|
179
217
|
}
|
|
180
|
-
/** Name of the currently loaded model */
|
|
218
|
+
/** Name of the currently loaded model. */
|
|
181
219
|
get currentModel() {
|
|
182
220
|
return this.modelName;
|
|
183
221
|
}
|
|
184
|
-
async storeInCache(file) {
|
|
185
|
-
const url = `${window.location.origin}/wllama-local-models/${file.name}`;
|
|
186
|
-
const cache = await caches.open("wllama-local-models");
|
|
187
|
-
await cache.put(
|
|
188
|
-
url,
|
|
189
|
-
new Response(file, {
|
|
190
|
-
headers: { "Content-Type": "application/octet-stream" }
|
|
191
|
-
})
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
async deleteFromCache(fileName) {
|
|
195
|
-
const url = `${window.location.origin}/wllama-local-models/${fileName}`;
|
|
196
|
-
const cache = await caches.open("wllama-local-models");
|
|
197
|
-
await cache.delete(url);
|
|
198
|
-
}
|
|
199
222
|
};
|
|
200
223
|
// Annotate the CommonJS export names for ESM import in node:
|
|
201
224
|
0 && (module.exports = {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/WllamaService.ts"],"sourcesContent":["export { WllamaService } from './WllamaService';\nexport type {\n WllamaServiceConfig,\n GenerateRequest,\n GenerateResult,\n LoadModelResult,\n ConnectionResult,\n} from './types';","import { Wllama } from '@wllama/wllama/esm/index.js';\nimport type {\n WllamaServiceConfig,\n GenerateRequest,\n GenerateResult,\n LoadModelResult,\n ConnectionResult,\n} from './types';\n\nexport class WllamaService {\n private wllama: Wllama | null = null;\n private modelName: string = '';\n private config: Required<WllamaServiceConfig>;\n\n constructor(config: WllamaServiceConfig = {}) {\n this.config = {\n wasmPath: config.wasmPath ?? '/wllama/wllama.wasm',\n nGpuLayers: config.nGpuLayers ?? 999,\n nCtx: config.nCtx ?? 2048,\n };\n }\n\n /** Check browser compatibility and capabilities */\n checkEnvironment(): ConnectionResult {\n if (typeof WebAssembly === 'undefined') {\n return { success: false, error: 'WebAssembly is not supported in this browser.' };\n }\n if (!('caches' in window)) {\n return { success: false, error: 'Cache API is not supported in this browser.' };\n }\n const hasWebGPU = !!(navigator as any).gpu;\n const isolated = window.crossOriginIsolated;\n return {\n success: true,\n hasWebGPU,\n crossOriginIsolated: isolated,\n };\n }\n\n /** Load a GGUF model file from the user's filesystem */\n async loadModel(\n file: File,\n onProgress?: (progress: number) => void,\n ): Promise<LoadModelResult> {\n const env = this.checkEnvironment();\n if (!env.success) return { success: false, error: env.error };\n\n await this.unload();\n\n try {\n onProgress?.(10);\n\n // AssetsPathConfig requires `default` — this is the key the\n // Wllama runtime actually reads (confirmed against the real\n // @wllama/wllama@3.5.1 implementation, not just its docs).\n // No `as any` needed: this shape is type-correct as written.\n const configPaths = { default: this.config.wasmPath };\n this.wllama = new Wllama(configPaths);\n onProgress?.(30);\n\n await this.storeInCache(file);\n onProgress?.(50);\n\n const hasWebGPU = !!(navigator as any).gpu;\n await (this.wllama as any).loadModel([file], {\n n_ctx: this.config.nCtx,\n ...(hasWebGPU && this.config.nGpuLayers > 0\n ? { n_gpu_layers: this.config.nGpuLayers }\n : {}),\n });\n\n this.modelName = file.name;\n onProgress?.(100);\n\n return { success: true, usedWebGPU: hasWebGPU };\n } catch (e: unknown) {\n await this.unload();\n const message = e instanceof Error ? e.message : 'Failed to load model';\n return { success: false, error: message };\n }\n }\n\n /**\n * Generate a response using the model's chat template.\n * Use this for normal conversational / instruction-following use cases —\n * it accepts a `messages`-style request (system + user) and\n * createChatCompletion formats it with the model's real chat template\n * (ChatML, Llama-3, etc.) before running inference.\n */\n async generate(req: GenerateRequest): Promise<GenerateResult> {\n if (!this.wllama) {\n return { success: false, error: 'No model loaded. Call loadModel() first.' };\n }\n\n try {\n const t0 = performance.now();\n\n const messages: { role: string; content: string }[] = [];\n if (req.system) messages.push({ role: 'system', content: req.system });\n messages.push({ role: 'user', content: req.prompt });\n\n const response = await (this.wllama as any).createChatCompletion({\n messages,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n });\n\n const text: string = response?.choices?.[0]?.message?.content ?? '';\n return {\n success: true,\n text,\n timeMs: Math.round(performance.now() - t0),\n };\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'Generation failed';\n return { success: false, error: message };\n }\n }\n\n /**\n * Generate a raw completion with no chat-template formatting.\n * Use this when you need to control the exact prompt string yourself —\n * e.g. few-shot prompts, base-model completion, custom templates, or\n * fill-in-the-middle style prompts.\n *\n * createCompletion (confirmed against @wllama/wllama@3.5.1's real type\n * defs) takes a single options object with a raw `prompt` string. It does\n * NOT accept a `messages` array and does NOT apply any chat template —\n * whatever string you pass is exactly what the model sees. `req.system`\n * (if provided) is prepended as plain text; it is your responsibility to\n * format it however the target model expects (call\n * `this.wllama.getChatTemplate()` if you want to match the model's\n * native template manually).\n */\n async generateCompletion(req: GenerateRequest): Promise<GenerateResult> {\n if (!this.wllama) {\n return { success: false, error: 'No model loaded. Call loadModel() first.' };\n }\n\n try {\n const t0 = performance.now();\n\n const prompt = req.system ? `${req.system}\\n\\n${req.prompt}` : req.prompt;\n\n const response = await (this.wllama as any).createCompletion({\n prompt,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n });\n\n const text: string = response?.choices?.[0]?.text ?? '';\n return {\n success: true,\n text,\n timeMs: Math.round(performance.now() - t0),\n };\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'Generation failed';\n return { success: false, error: message };\n }\n }\n\n /** Unload the current model and free memory */\n async unload(): Promise<void> {\n if (this.wllama) {\n try {\n await (this.wllama as any).exit();\n } catch (_) {\n /* ignore */\n }\n this.wllama = null;\n }\n if (this.modelName) {\n try {\n await this.deleteFromCache(this.modelName);\n } catch (_) {\n /* ignore */\n }\n this.modelName = '';\n }\n }\n\n /** Whether a model is currently loaded */\n get isLoaded(): boolean {\n return this.wllama !== null;\n }\n\n /** Name of the currently loaded model */\n get currentModel(): string {\n return this.modelName;\n }\n\n private async storeInCache(file: File): Promise<void> {\n const url = `${window.location.origin}/wllama-local-models/${file.name}`;\n const cache = await caches.open('wllama-local-models');\n await cache.put(\n url,\n new Response(file, {\n headers: { 'Content-Type': 'application/octet-stream' },\n }),\n );\n }\n\n private async deleteFromCache(fileName: string): Promise<void> {\n const url = `${window.location.origin}/wllama-local-models/${fileName}`;\n const cache = await caches.open('wllama-local-models');\n await cache.delete(url);\n }\n}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/WllamaService.ts"],"sourcesContent":["export { WllamaService } from './WllamaService';\nexport type {\n WllamaServiceConfig,\n GenerateRequest,\n GenerateResult,\n LoadModelResult,\n ConnectionResult,\n} from './types';","// src/WllamaService.ts\n\nimport { Wllama } from \"@wllama/wllama/esm/index.js\";\n\nimport type {\n WllamaServiceConfig,\n GenerateRequest,\n GenerateResult,\n LoadModelResult,\n ConnectionResult,\n} from \"./types\";\n\nexport class WllamaService {\n private wllama: Wllama | null = null;\n private modelName = \"\";\n private readonly config: Required<WllamaServiceConfig>;\n\n constructor(config: WllamaServiceConfig = {}) {\n this.config = {\n wasmPath: config.wasmPath ?? \"/wllama/wllama.wasm\",\n nGpuLayers: config.nGpuLayers ?? 999,\n nCtx: config.nCtx ?? 2048,\n };\n }\n\n /**\n * Check WebGPU without directly accessing navigator.gpu.\n *\n * This avoids requiring @webgpu/types merely for feature detection.\n */\n private hasWebGPU(): boolean {\n return typeof navigator !== \"undefined\" && \"gpu\" in navigator;\n }\n\n /** Check browser compatibility and capabilities. */\n checkEnvironment(): ConnectionResult {\n if (\n typeof window === \"undefined\" ||\n typeof navigator === \"undefined\"\n ) {\n return {\n success: false,\n error: \"WllamaService can only run in a browser environment.\",\n };\n }\n\n if (typeof WebAssembly === \"undefined\") {\n return {\n success: false,\n error: \"WebAssembly is not supported in this browser.\",\n };\n }\n\n return {\n success: true,\n hasWebGPU: this.hasWebGPU(),\n crossOriginIsolated: window.crossOriginIsolated,\n };\n }\n\n /** Load a GGUF model file from the user's filesystem. */\n async loadModel(\n file: File,\n onProgress?: (progress: number) => void,\n ): Promise<LoadModelResult> {\n const environment = this.checkEnvironment();\n\n if (!environment.success) {\n return {\n success: false,\n error: environment.error,\n };\n }\n\n await this.unload();\n\n try {\n onProgress?.(10);\n\n this.wllama = new Wllama({\n default: this.config.wasmPath,\n });\n\n onProgress?.(30);\n\n const useWebGPU =\n this.hasWebGPU() && this.config.nGpuLayers > 0;\n\n await this.wllama.loadModel([file], {\n n_ctx: this.config.nCtx,\n n_gpu_layers: useWebGPU\n ? this.config.nGpuLayers\n : 0,\n jinja: true,\n });\n\n this.modelName = file.name;\n\n onProgress?.(100);\n\n return {\n success: true,\n usedWebGPU: useWebGPU,\n };\n } catch (error: unknown) {\n await this.unload();\n\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Failed to load model\",\n };\n }\n }\n\n /**\n * Generate a chat response using the model's embedded chat template.\n *\n * Suitable for instruction-tuned models such as Phi-3 Instruct,\n * Llama Instruct, ChatML models, and similar chat models.\n */\n async generate(\n req: GenerateRequest,\n ): Promise<GenerateResult> {\n if (!this.wllama) {\n return {\n success: false,\n error: \"No model loaded. Call loadModel() first.\",\n };\n }\n\n try {\n const startedAt = performance.now();\n\n const messages: Array<{\n role: \"system\" | \"user\";\n content: string;\n }> = [];\n\n if (req.system) {\n messages.push({\n role: \"system\",\n content: req.system,\n });\n }\n\n messages.push({\n role: \"user\",\n content: req.prompt,\n });\n\n /*\n * Keep the cast here if the installed @wllama/wllama declaration\n * does not expose the exact createChatCompletion signature.\n */\n const response = await (\n this.wllama as unknown as {\n createChatCompletion(options: {\n messages: Array<{\n role: \"system\" | \"user\";\n content: string;\n }>;\n stream: false;\n max_tokens: number;\n temperature: number;\n top_k: number;\n top_p: number;\n stop?: string | string[];\n abortSignal?: AbortSignal;\n }): Promise<{\n choices?: Array<{\n message?: {\n content?: string;\n };\n }>;\n }>;\n }\n ).createChatCompletion({\n messages,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n stop: req.stop,\n abortSignal: req.abortSignal,\n });\n\n const text =\n response.choices?.[0]?.message?.content ?? \"\";\n\n return {\n success: true,\n text,\n timeMs: Math.round(\n performance.now() - startedAt,\n ),\n };\n } catch (error: unknown) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Generation failed\",\n };\n }\n }\n\n /**\n * Generate a raw completion without applying a chat template.\n *\n * Suitable for base completion models, manually formatted prompts,\n * few-shot completion, fill-in-the-middle, and custom templates.\n */\n async generateCompletion(\n req: GenerateRequest,\n ): Promise<GenerateResult> {\n if (!this.wllama) {\n return {\n success: false,\n error: \"No model loaded. Call loadModel() first.\",\n };\n }\n\n try {\n const startedAt = performance.now();\n\n const prompt = req.system\n ? `${req.system}\\n\\n${req.prompt}`\n : req.prompt;\n\n const response =\n await this.wllama.createCompletion({\n prompt,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n stop: req.stop,\n abortSignal: req.abortSignal,\n });\n\n const choice = response.choices?.[0];\n\n console.debug(\"[wllama] raw completion\", {\n text: choice?.text,\n finishReason: choice?.finish_reason,\n usage: response.usage,\n timings: response.timings,\n });\n\n return {\n success: true,\n text: choice?.text ?? \"\",\n timeMs: Math.round(\n performance.now() - startedAt,\n ),\n };\n } catch (error: unknown) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Generation failed\",\n };\n }\n }\n\n /** Unload the current model and release its resources. */\n async unload(): Promise<void> {\n if (this.wllama) {\n try {\n await this.wllama.exit();\n } catch {\n // Ignore shutdown errors.\n }\n\n this.wllama = null;\n }\n\n this.modelName = \"\";\n }\n\n /** Whether a model is currently loaded. */\n get isLoaded(): boolean {\n return this.wllama !== null;\n }\n\n /** Name of the currently loaded model. */\n get currentModel(): string {\n return this.modelName;\n }\n}"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -10,104 +10,136 @@ var WllamaService = class {
|
|
|
10
10
|
nCtx: config.nCtx ?? 2048
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Check WebGPU without directly accessing navigator.gpu.
|
|
15
|
+
*
|
|
16
|
+
* This avoids requiring @webgpu/types merely for feature detection.
|
|
17
|
+
*/
|
|
18
|
+
hasWebGPU() {
|
|
19
|
+
return typeof navigator !== "undefined" && "gpu" in navigator;
|
|
20
|
+
}
|
|
21
|
+
/** Check browser compatibility and capabilities. */
|
|
14
22
|
checkEnvironment() {
|
|
15
|
-
if (typeof
|
|
16
|
-
return {
|
|
23
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
error: "WllamaService can only run in a browser environment."
|
|
27
|
+
};
|
|
17
28
|
}
|
|
18
|
-
if (
|
|
19
|
-
return {
|
|
29
|
+
if (typeof WebAssembly === "undefined") {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: "WebAssembly is not supported in this browser."
|
|
33
|
+
};
|
|
20
34
|
}
|
|
21
|
-
const hasWebGPU = !!navigator.gpu;
|
|
22
|
-
const isolated = window.crossOriginIsolated;
|
|
23
35
|
return {
|
|
24
36
|
success: true,
|
|
25
|
-
hasWebGPU,
|
|
26
|
-
crossOriginIsolated:
|
|
37
|
+
hasWebGPU: this.hasWebGPU(),
|
|
38
|
+
crossOriginIsolated: window.crossOriginIsolated
|
|
27
39
|
};
|
|
28
40
|
}
|
|
29
|
-
/** Load a GGUF model file from the user's filesystem */
|
|
41
|
+
/** Load a GGUF model file from the user's filesystem. */
|
|
30
42
|
async loadModel(file, onProgress) {
|
|
31
|
-
const
|
|
32
|
-
if (!
|
|
43
|
+
const environment = this.checkEnvironment();
|
|
44
|
+
if (!environment.success) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: environment.error
|
|
48
|
+
};
|
|
49
|
+
}
|
|
33
50
|
await this.unload();
|
|
34
51
|
try {
|
|
35
52
|
onProgress?.(10);
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
this.wllama = new Wllama({
|
|
54
|
+
default: this.config.wasmPath
|
|
55
|
+
});
|
|
38
56
|
onProgress?.(30);
|
|
39
|
-
|
|
40
|
-
onProgress?.(50);
|
|
41
|
-
const hasWebGPU = !!navigator.gpu;
|
|
57
|
+
const useWebGPU = this.hasWebGPU() && this.config.nGpuLayers > 0;
|
|
42
58
|
await this.wllama.loadModel([file], {
|
|
43
59
|
n_ctx: this.config.nCtx,
|
|
44
|
-
|
|
60
|
+
n_gpu_layers: useWebGPU ? this.config.nGpuLayers : 0,
|
|
61
|
+
jinja: true
|
|
45
62
|
});
|
|
46
63
|
this.modelName = file.name;
|
|
47
64
|
onProgress?.(100);
|
|
48
|
-
return {
|
|
49
|
-
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
usedWebGPU: useWebGPU
|
|
68
|
+
};
|
|
69
|
+
} catch (error) {
|
|
50
70
|
await this.unload();
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: error instanceof Error ? error.message : "Failed to load model"
|
|
74
|
+
};
|
|
53
75
|
}
|
|
54
76
|
}
|
|
55
77
|
/**
|
|
56
|
-
* Generate a response using the model's chat template.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* (ChatML, Llama-3, etc.) before running inference.
|
|
78
|
+
* Generate a chat response using the model's embedded chat template.
|
|
79
|
+
*
|
|
80
|
+
* Suitable for instruction-tuned models such as Phi-3 Instruct,
|
|
81
|
+
* Llama Instruct, ChatML models, and similar chat models.
|
|
61
82
|
*/
|
|
62
83
|
async generate(req) {
|
|
63
84
|
if (!this.wllama) {
|
|
64
|
-
return {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: "No model loaded. Call loadModel() first."
|
|
88
|
+
};
|
|
65
89
|
}
|
|
66
90
|
try {
|
|
67
|
-
const
|
|
91
|
+
const startedAt = performance.now();
|
|
68
92
|
const messages = [];
|
|
69
|
-
if (req.system)
|
|
70
|
-
|
|
93
|
+
if (req.system) {
|
|
94
|
+
messages.push({
|
|
95
|
+
role: "system",
|
|
96
|
+
content: req.system
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
messages.push({
|
|
100
|
+
role: "user",
|
|
101
|
+
content: req.prompt
|
|
102
|
+
});
|
|
71
103
|
const response = await this.wllama.createChatCompletion({
|
|
72
104
|
messages,
|
|
73
105
|
stream: false,
|
|
74
106
|
max_tokens: req.maxTokens ?? 512,
|
|
75
107
|
temperature: req.temperature ?? 0.7,
|
|
76
108
|
top_k: req.topK ?? 40,
|
|
77
|
-
top_p: req.topP ?? 0.95
|
|
109
|
+
top_p: req.topP ?? 0.95,
|
|
110
|
+
stop: req.stop,
|
|
111
|
+
abortSignal: req.abortSignal
|
|
78
112
|
});
|
|
79
|
-
const text = response
|
|
113
|
+
const text = response.choices?.[0]?.message?.content ?? "";
|
|
80
114
|
return {
|
|
81
115
|
success: true,
|
|
82
116
|
text,
|
|
83
|
-
timeMs: Math.round(
|
|
117
|
+
timeMs: Math.round(
|
|
118
|
+
performance.now() - startedAt
|
|
119
|
+
)
|
|
120
|
+
};
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: error instanceof Error ? error.message : "Generation failed"
|
|
84
125
|
};
|
|
85
|
-
} catch (e) {
|
|
86
|
-
const message = e instanceof Error ? e.message : "Generation failed";
|
|
87
|
-
return { success: false, error: message };
|
|
88
126
|
}
|
|
89
127
|
}
|
|
90
128
|
/**
|
|
91
|
-
* Generate a raw completion
|
|
92
|
-
* Use this when you need to control the exact prompt string yourself —
|
|
93
|
-
* e.g. few-shot prompts, base-model completion, custom templates, or
|
|
94
|
-
* fill-in-the-middle style prompts.
|
|
129
|
+
* Generate a raw completion without applying a chat template.
|
|
95
130
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* NOT accept a `messages` array and does NOT apply any chat template —
|
|
99
|
-
* whatever string you pass is exactly what the model sees. `req.system`
|
|
100
|
-
* (if provided) is prepended as plain text; it is your responsibility to
|
|
101
|
-
* format it however the target model expects (call
|
|
102
|
-
* `this.wllama.getChatTemplate()` if you want to match the model's
|
|
103
|
-
* native template manually).
|
|
131
|
+
* Suitable for base completion models, manually formatted prompts,
|
|
132
|
+
* few-shot completion, fill-in-the-middle, and custom templates.
|
|
104
133
|
*/
|
|
105
134
|
async generateCompletion(req) {
|
|
106
135
|
if (!this.wllama) {
|
|
107
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
error: "No model loaded. Call loadModel() first."
|
|
139
|
+
};
|
|
108
140
|
}
|
|
109
141
|
try {
|
|
110
|
-
const
|
|
142
|
+
const startedAt = performance.now();
|
|
111
143
|
const prompt = req.system ? `${req.system}
|
|
112
144
|
|
|
113
145
|
${req.prompt}` : req.prompt;
|
|
@@ -117,59 +149,50 @@ ${req.prompt}` : req.prompt;
|
|
|
117
149
|
max_tokens: req.maxTokens ?? 512,
|
|
118
150
|
temperature: req.temperature ?? 0.7,
|
|
119
151
|
top_k: req.topK ?? 40,
|
|
120
|
-
top_p: req.topP ?? 0.95
|
|
152
|
+
top_p: req.topP ?? 0.95,
|
|
153
|
+
stop: req.stop,
|
|
154
|
+
abortSignal: req.abortSignal
|
|
155
|
+
});
|
|
156
|
+
const choice = response.choices?.[0];
|
|
157
|
+
console.debug("[wllama] raw completion", {
|
|
158
|
+
text: choice?.text,
|
|
159
|
+
finishReason: choice?.finish_reason,
|
|
160
|
+
usage: response.usage,
|
|
161
|
+
timings: response.timings
|
|
121
162
|
});
|
|
122
|
-
const text = response?.choices?.[0]?.text ?? "";
|
|
123
163
|
return {
|
|
124
164
|
success: true,
|
|
125
|
-
text,
|
|
126
|
-
timeMs: Math.round(
|
|
165
|
+
text: choice?.text ?? "",
|
|
166
|
+
timeMs: Math.round(
|
|
167
|
+
performance.now() - startedAt
|
|
168
|
+
)
|
|
169
|
+
};
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: error instanceof Error ? error.message : "Generation failed"
|
|
127
174
|
};
|
|
128
|
-
} catch (e) {
|
|
129
|
-
const message = e instanceof Error ? e.message : "Generation failed";
|
|
130
|
-
return { success: false, error: message };
|
|
131
175
|
}
|
|
132
176
|
}
|
|
133
|
-
/** Unload the current model and
|
|
177
|
+
/** Unload the current model and release its resources. */
|
|
134
178
|
async unload() {
|
|
135
179
|
if (this.wllama) {
|
|
136
180
|
try {
|
|
137
181
|
await this.wllama.exit();
|
|
138
|
-
} catch
|
|
182
|
+
} catch {
|
|
139
183
|
}
|
|
140
184
|
this.wllama = null;
|
|
141
185
|
}
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
await this.deleteFromCache(this.modelName);
|
|
145
|
-
} catch (_) {
|
|
146
|
-
}
|
|
147
|
-
this.modelName = "";
|
|
148
|
-
}
|
|
186
|
+
this.modelName = "";
|
|
149
187
|
}
|
|
150
|
-
/** Whether a model is currently loaded */
|
|
188
|
+
/** Whether a model is currently loaded. */
|
|
151
189
|
get isLoaded() {
|
|
152
190
|
return this.wllama !== null;
|
|
153
191
|
}
|
|
154
|
-
/** Name of the currently loaded model */
|
|
192
|
+
/** Name of the currently loaded model. */
|
|
155
193
|
get currentModel() {
|
|
156
194
|
return this.modelName;
|
|
157
195
|
}
|
|
158
|
-
async storeInCache(file) {
|
|
159
|
-
const url = `${window.location.origin}/wllama-local-models/${file.name}`;
|
|
160
|
-
const cache = await caches.open("wllama-local-models");
|
|
161
|
-
await cache.put(
|
|
162
|
-
url,
|
|
163
|
-
new Response(file, {
|
|
164
|
-
headers: { "Content-Type": "application/octet-stream" }
|
|
165
|
-
})
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
async deleteFromCache(fileName) {
|
|
169
|
-
const url = `${window.location.origin}/wllama-local-models/${fileName}`;
|
|
170
|
-
const cache = await caches.open("wllama-local-models");
|
|
171
|
-
await cache.delete(url);
|
|
172
|
-
}
|
|
173
196
|
};
|
|
174
197
|
export {
|
|
175
198
|
WllamaService
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/WllamaService.ts"],"sourcesContent":["import { Wllama } from '@wllama/wllama/esm/index.js';\nimport type {\n WllamaServiceConfig,\n GenerateRequest,\n GenerateResult,\n LoadModelResult,\n ConnectionResult,\n} from './types';\n\nexport class WllamaService {\n private wllama: Wllama | null = null;\n private modelName: string = '';\n private config: Required<WllamaServiceConfig>;\n\n constructor(config: WllamaServiceConfig = {}) {\n this.config = {\n wasmPath: config.wasmPath ?? '/wllama/wllama.wasm',\n nGpuLayers: config.nGpuLayers ?? 999,\n nCtx: config.nCtx ?? 2048,\n };\n }\n\n /** Check browser compatibility and capabilities */\n checkEnvironment(): ConnectionResult {\n if (typeof WebAssembly === 'undefined') {\n return { success: false, error: 'WebAssembly is not supported in this browser.' };\n }\n if (!('caches' in window)) {\n return { success: false, error: 'Cache API is not supported in this browser.' };\n }\n const hasWebGPU = !!(navigator as any).gpu;\n const isolated = window.crossOriginIsolated;\n return {\n success: true,\n hasWebGPU,\n crossOriginIsolated: isolated,\n };\n }\n\n /** Load a GGUF model file from the user's filesystem */\n async loadModel(\n file: File,\n onProgress?: (progress: number) => void,\n ): Promise<LoadModelResult> {\n const env = this.checkEnvironment();\n if (!env.success) return { success: false, error: env.error };\n\n await this.unload();\n\n try {\n onProgress?.(10);\n\n // AssetsPathConfig requires `default` — this is the key the\n // Wllama runtime actually reads (confirmed against the real\n // @wllama/wllama@3.5.1 implementation, not just its docs).\n // No `as any` needed: this shape is type-correct as written.\n const configPaths = { default: this.config.wasmPath };\n this.wllama = new Wllama(configPaths);\n onProgress?.(30);\n\n await this.storeInCache(file);\n onProgress?.(50);\n\n const hasWebGPU = !!(navigator as any).gpu;\n await (this.wllama as any).loadModel([file], {\n n_ctx: this.config.nCtx,\n ...(hasWebGPU && this.config.nGpuLayers > 0\n ? { n_gpu_layers: this.config.nGpuLayers }\n : {}),\n });\n\n this.modelName = file.name;\n onProgress?.(100);\n\n return { success: true, usedWebGPU: hasWebGPU };\n } catch (e: unknown) {\n await this.unload();\n const message = e instanceof Error ? e.message : 'Failed to load model';\n return { success: false, error: message };\n }\n }\n\n /**\n * Generate a response using the model's chat template.\n * Use this for normal conversational / instruction-following use cases —\n * it accepts a `messages`-style request (system + user) and\n * createChatCompletion formats it with the model's real chat template\n * (ChatML, Llama-3, etc.) before running inference.\n */\n async generate(req: GenerateRequest): Promise<GenerateResult> {\n if (!this.wllama) {\n return { success: false, error: 'No model loaded. Call loadModel() first.' };\n }\n\n try {\n const t0 = performance.now();\n\n const messages: { role: string; content: string }[] = [];\n if (req.system) messages.push({ role: 'system', content: req.system });\n messages.push({ role: 'user', content: req.prompt });\n\n const response = await (this.wllama as any).createChatCompletion({\n messages,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n });\n\n const text: string = response?.choices?.[0]?.message?.content ?? '';\n return {\n success: true,\n text,\n timeMs: Math.round(performance.now() - t0),\n };\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'Generation failed';\n return { success: false, error: message };\n }\n }\n\n /**\n * Generate a raw completion with no chat-template formatting.\n * Use this when you need to control the exact prompt string yourself —\n * e.g. few-shot prompts, base-model completion, custom templates, or\n * fill-in-the-middle style prompts.\n *\n * createCompletion (confirmed against @wllama/wllama@3.5.1's real type\n * defs) takes a single options object with a raw `prompt` string. It does\n * NOT accept a `messages` array and does NOT apply any chat template —\n * whatever string you pass is exactly what the model sees. `req.system`\n * (if provided) is prepended as plain text; it is your responsibility to\n * format it however the target model expects (call\n * `this.wllama.getChatTemplate()` if you want to match the model's\n * native template manually).\n */\n async generateCompletion(req: GenerateRequest): Promise<GenerateResult> {\n if (!this.wllama) {\n return { success: false, error: 'No model loaded. Call loadModel() first.' };\n }\n\n try {\n const t0 = performance.now();\n\n const prompt = req.system ? `${req.system}\\n\\n${req.prompt}` : req.prompt;\n\n const response = await (this.wllama as any).createCompletion({\n prompt,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n });\n\n const text: string = response?.choices?.[0]?.text ?? '';\n return {\n success: true,\n text,\n timeMs: Math.round(performance.now() - t0),\n };\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'Generation failed';\n return { success: false, error: message };\n }\n }\n\n /** Unload the current model and free memory */\n async unload(): Promise<void> {\n if (this.wllama) {\n try {\n await (this.wllama as any).exit();\n } catch (_) {\n /* ignore */\n }\n this.wllama = null;\n }\n if (this.modelName) {\n try {\n await this.deleteFromCache(this.modelName);\n } catch (_) {\n /* ignore */\n }\n this.modelName = '';\n }\n }\n\n /** Whether a model is currently loaded */\n get isLoaded(): boolean {\n return this.wllama !== null;\n }\n\n /** Name of the currently loaded model */\n get currentModel(): string {\n return this.modelName;\n }\n\n private async storeInCache(file: File): Promise<void> {\n const url = `${window.location.origin}/wllama-local-models/${file.name}`;\n const cache = await caches.open('wllama-local-models');\n await cache.put(\n url,\n new Response(file, {\n headers: { 'Content-Type': 'application/octet-stream' },\n }),\n );\n }\n\n private async deleteFromCache(fileName: string): Promise<void> {\n const url = `${window.location.origin}/wllama-local-models/${fileName}`;\n const cache = await caches.open('wllama-local-models');\n await cache.delete(url);\n }\n}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/WllamaService.ts"],"sourcesContent":["// src/WllamaService.ts\n\nimport { Wllama } from \"@wllama/wllama/esm/index.js\";\n\nimport type {\n WllamaServiceConfig,\n GenerateRequest,\n GenerateResult,\n LoadModelResult,\n ConnectionResult,\n} from \"./types\";\n\nexport class WllamaService {\n private wllama: Wllama | null = null;\n private modelName = \"\";\n private readonly config: Required<WllamaServiceConfig>;\n\n constructor(config: WllamaServiceConfig = {}) {\n this.config = {\n wasmPath: config.wasmPath ?? \"/wllama/wllama.wasm\",\n nGpuLayers: config.nGpuLayers ?? 999,\n nCtx: config.nCtx ?? 2048,\n };\n }\n\n /**\n * Check WebGPU without directly accessing navigator.gpu.\n *\n * This avoids requiring @webgpu/types merely for feature detection.\n */\n private hasWebGPU(): boolean {\n return typeof navigator !== \"undefined\" && \"gpu\" in navigator;\n }\n\n /** Check browser compatibility and capabilities. */\n checkEnvironment(): ConnectionResult {\n if (\n typeof window === \"undefined\" ||\n typeof navigator === \"undefined\"\n ) {\n return {\n success: false,\n error: \"WllamaService can only run in a browser environment.\",\n };\n }\n\n if (typeof WebAssembly === \"undefined\") {\n return {\n success: false,\n error: \"WebAssembly is not supported in this browser.\",\n };\n }\n\n return {\n success: true,\n hasWebGPU: this.hasWebGPU(),\n crossOriginIsolated: window.crossOriginIsolated,\n };\n }\n\n /** Load a GGUF model file from the user's filesystem. */\n async loadModel(\n file: File,\n onProgress?: (progress: number) => void,\n ): Promise<LoadModelResult> {\n const environment = this.checkEnvironment();\n\n if (!environment.success) {\n return {\n success: false,\n error: environment.error,\n };\n }\n\n await this.unload();\n\n try {\n onProgress?.(10);\n\n this.wllama = new Wllama({\n default: this.config.wasmPath,\n });\n\n onProgress?.(30);\n\n const useWebGPU =\n this.hasWebGPU() && this.config.nGpuLayers > 0;\n\n await this.wllama.loadModel([file], {\n n_ctx: this.config.nCtx,\n n_gpu_layers: useWebGPU\n ? this.config.nGpuLayers\n : 0,\n jinja: true,\n });\n\n this.modelName = file.name;\n\n onProgress?.(100);\n\n return {\n success: true,\n usedWebGPU: useWebGPU,\n };\n } catch (error: unknown) {\n await this.unload();\n\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Failed to load model\",\n };\n }\n }\n\n /**\n * Generate a chat response using the model's embedded chat template.\n *\n * Suitable for instruction-tuned models such as Phi-3 Instruct,\n * Llama Instruct, ChatML models, and similar chat models.\n */\n async generate(\n req: GenerateRequest,\n ): Promise<GenerateResult> {\n if (!this.wllama) {\n return {\n success: false,\n error: \"No model loaded. Call loadModel() first.\",\n };\n }\n\n try {\n const startedAt = performance.now();\n\n const messages: Array<{\n role: \"system\" | \"user\";\n content: string;\n }> = [];\n\n if (req.system) {\n messages.push({\n role: \"system\",\n content: req.system,\n });\n }\n\n messages.push({\n role: \"user\",\n content: req.prompt,\n });\n\n /*\n * Keep the cast here if the installed @wllama/wllama declaration\n * does not expose the exact createChatCompletion signature.\n */\n const response = await (\n this.wllama as unknown as {\n createChatCompletion(options: {\n messages: Array<{\n role: \"system\" | \"user\";\n content: string;\n }>;\n stream: false;\n max_tokens: number;\n temperature: number;\n top_k: number;\n top_p: number;\n stop?: string | string[];\n abortSignal?: AbortSignal;\n }): Promise<{\n choices?: Array<{\n message?: {\n content?: string;\n };\n }>;\n }>;\n }\n ).createChatCompletion({\n messages,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n stop: req.stop,\n abortSignal: req.abortSignal,\n });\n\n const text =\n response.choices?.[0]?.message?.content ?? \"\";\n\n return {\n success: true,\n text,\n timeMs: Math.round(\n performance.now() - startedAt,\n ),\n };\n } catch (error: unknown) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Generation failed\",\n };\n }\n }\n\n /**\n * Generate a raw completion without applying a chat template.\n *\n * Suitable for base completion models, manually formatted prompts,\n * few-shot completion, fill-in-the-middle, and custom templates.\n */\n async generateCompletion(\n req: GenerateRequest,\n ): Promise<GenerateResult> {\n if (!this.wllama) {\n return {\n success: false,\n error: \"No model loaded. Call loadModel() first.\",\n };\n }\n\n try {\n const startedAt = performance.now();\n\n const prompt = req.system\n ? `${req.system}\\n\\n${req.prompt}`\n : req.prompt;\n\n const response =\n await this.wllama.createCompletion({\n prompt,\n stream: false,\n max_tokens: req.maxTokens ?? 512,\n temperature: req.temperature ?? 0.7,\n top_k: req.topK ?? 40,\n top_p: req.topP ?? 0.95,\n stop: req.stop,\n abortSignal: req.abortSignal,\n });\n\n const choice = response.choices?.[0];\n\n console.debug(\"[wllama] raw completion\", {\n text: choice?.text,\n finishReason: choice?.finish_reason,\n usage: response.usage,\n timings: response.timings,\n });\n\n return {\n success: true,\n text: choice?.text ?? \"\",\n timeMs: Math.round(\n performance.now() - startedAt,\n ),\n };\n } catch (error: unknown) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Generation failed\",\n };\n }\n }\n\n /** Unload the current model and release its resources. */\n async unload(): Promise<void> {\n if (this.wllama) {\n try {\n await this.wllama.exit();\n } catch {\n // Ignore shutdown errors.\n }\n\n this.wllama = null;\n }\n\n this.modelName = \"\";\n }\n\n /** Whether a model is currently loaded. */\n get isLoaded(): boolean {\n return this.wllama !== null;\n }\n\n /** Name of the currently loaded model. */\n get currentModel(): string {\n return this.modelName;\n }\n}"],"mappings":"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","names":[]}
|