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 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
- /** Check browser compatibility and capabilities */
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
- * Use this for normal conversational / instruction-following use cases —
47
- * it accepts a `messages`-style request (system + user) and
48
- * createChatCompletion formats it with the model's real chat template
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 with no chat-template formatting.
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
- * createCompletion (confirmed against @wllama/wllama@3.5.1's real type
59
- * defs) takes a single options object with a raw `prompt` string. It does
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 free memory */
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
- /** Check browser compatibility and capabilities */
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
- * Use this for normal conversational / instruction-following use cases —
47
- * it accepts a `messages`-style request (system + user) and
48
- * createChatCompletion formats it with the model's real chat template
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 with no chat-template formatting.
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
- * createCompletion (confirmed against @wllama/wllama@3.5.1's real type
59
- * defs) takes a single options object with a raw `prompt` string. It does
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 free memory */
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
- /** Check browser compatibility and capabilities */
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 WebAssembly === "undefined") {
42
- return { success: false, error: "WebAssembly is not supported in this browser." };
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 (!("caches" in window)) {
45
- return { success: false, error: "Cache API is not supported in this browser." };
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: isolated
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 env = this.checkEnvironment();
58
- if (!env.success) return { success: false, error: env.error };
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
- const configPaths = { default: this.config.wasmPath };
63
- this.wllama = new import_esm.Wllama(configPaths);
79
+ this.wllama = new import_esm.Wllama({
80
+ default: this.config.wasmPath
81
+ });
64
82
  onProgress?.(30);
65
- await this.storeInCache(file);
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
- ...hasWebGPU && this.config.nGpuLayers > 0 ? { n_gpu_layers: this.config.nGpuLayers } : {}
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 { success: true, usedWebGPU: hasWebGPU };
75
- } catch (e) {
91
+ return {
92
+ success: true,
93
+ usedWebGPU: useWebGPU
94
+ };
95
+ } catch (error) {
76
96
  await this.unload();
77
- const message = e instanceof Error ? e.message : "Failed to load model";
78
- return { success: false, error: message };
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
- * Use this for normal conversational / instruction-following use cases —
84
- * it accepts a `messages`-style request (system + user) and
85
- * createChatCompletion formats it with the model's real chat template
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 { success: false, error: "No model loaded. Call loadModel() first." };
111
+ return {
112
+ success: false,
113
+ error: "No model loaded. Call loadModel() first."
114
+ };
91
115
  }
92
116
  try {
93
- const t0 = performance.now();
117
+ const startedAt = performance.now();
94
118
  const messages = [];
95
- if (req.system) messages.push({ role: "system", content: req.system });
96
- messages.push({ role: "user", content: req.prompt });
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?.choices?.[0]?.message?.content ?? "";
139
+ const text = response.choices?.[0]?.message?.content ?? "";
106
140
  return {
107
141
  success: true,
108
142
  text,
109
- timeMs: Math.round(performance.now() - t0)
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 with no chat-template formatting.
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
- * createCompletion (confirmed against @wllama/wllama@3.5.1's real type
123
- * defs) takes a single options object with a raw `prompt` string. It does
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 { success: false, error: "No model loaded. Call loadModel() first." };
162
+ return {
163
+ success: false,
164
+ error: "No model loaded. Call loadModel() first."
165
+ };
134
166
  }
135
167
  try {
136
- const t0 = performance.now();
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(performance.now() - t0)
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 free memory */
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
- if (this.modelName) {
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
- /** Check browser compatibility and capabilities */
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 WebAssembly === "undefined") {
16
- return { success: false, error: "WebAssembly is not supported in this browser." };
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 (!("caches" in window)) {
19
- return { success: false, error: "Cache API is not supported in this browser." };
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: isolated
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 env = this.checkEnvironment();
32
- if (!env.success) return { success: false, error: env.error };
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
- const configPaths = { default: this.config.wasmPath };
37
- this.wllama = new Wllama(configPaths);
53
+ this.wllama = new Wllama({
54
+ default: this.config.wasmPath
55
+ });
38
56
  onProgress?.(30);
39
- await this.storeInCache(file);
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
- ...hasWebGPU && this.config.nGpuLayers > 0 ? { n_gpu_layers: this.config.nGpuLayers } : {}
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 { success: true, usedWebGPU: hasWebGPU };
49
- } catch (e) {
65
+ return {
66
+ success: true,
67
+ usedWebGPU: useWebGPU
68
+ };
69
+ } catch (error) {
50
70
  await this.unload();
51
- const message = e instanceof Error ? e.message : "Failed to load model";
52
- return { success: false, error: message };
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
- * Use this for normal conversational / instruction-following use cases —
58
- * it accepts a `messages`-style request (system + user) and
59
- * createChatCompletion formats it with the model's real chat template
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 { success: false, error: "No model loaded. Call loadModel() first." };
85
+ return {
86
+ success: false,
87
+ error: "No model loaded. Call loadModel() first."
88
+ };
65
89
  }
66
90
  try {
67
- const t0 = performance.now();
91
+ const startedAt = performance.now();
68
92
  const messages = [];
69
- if (req.system) messages.push({ role: "system", content: req.system });
70
- messages.push({ role: "user", content: req.prompt });
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?.choices?.[0]?.message?.content ?? "";
113
+ const text = response.choices?.[0]?.message?.content ?? "";
80
114
  return {
81
115
  success: true,
82
116
  text,
83
- timeMs: Math.round(performance.now() - t0)
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 with no chat-template formatting.
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
- * createCompletion (confirmed against @wllama/wllama@3.5.1's real type
97
- * defs) takes a single options object with a raw `prompt` string. It does
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 { success: false, error: "No model loaded. Call loadModel() first." };
136
+ return {
137
+ success: false,
138
+ error: "No model loaded. Call loadModel() first."
139
+ };
108
140
  }
109
141
  try {
110
- const t0 = performance.now();
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(performance.now() - t0)
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 free memory */
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
- if (this.modelName) {
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
@@ -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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wllama-service",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "Framework-agnostic browser LLM service wrapper with WebGPU and WebAssembly support",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",