@forbocai/browser 0.5.9 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,8 @@
1
- // src/cortex.ts
1
+ // src/browserCortexSlice.ts
2
+ import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit";
3
+ import { cortexInitStart, cortexInitSuccess, cortexInitFailed } from "@forbocai/core";
4
+ var _engines = {};
5
+ var browserCortexAdapter = createEntityAdapter();
2
6
  var DEFAULT_MODEL = "smollm2-135m";
3
7
  var MODEL_ALIASES = {
4
8
  "smollm2-135m": "SmolLM2-135M-Instruct-q0f16-MLC",
@@ -7,168 +11,312 @@ var MODEL_ALIASES = {
7
11
  "llama3-8b": "Llama-3.1-8B-Instruct-q4f16_1-MLC"
8
12
  };
9
13
  var resolveModelId = (alias) => MODEL_ALIASES[alias] ?? alias;
10
- var yieldTokens = async function* (iterator) {
11
- const { value, done } = await iterator.next();
12
- if (done) return;
13
- const content = value.choices[0]?.delta?.content || "";
14
- if (content) yield content;
15
- yield* yieldTokens(iterator);
16
- };
17
- var createBrowserCortex = (config = {}) => {
18
- const friendlyModel = config.model || DEFAULT_MODEL;
19
- const modelId = resolveModelId(friendlyModel);
20
- let _state = {
21
- engine: null,
22
- status: {
23
- id: "browser-init",
24
- model: friendlyModel,
25
- ready: false,
26
- engine: "web-llm"
27
- }
28
- };
29
- const init = async (onProgress) => {
30
- if (_state.status.ready) return _state.status;
14
+ var initBrowserCortexThunk = createAsyncThunk(
15
+ "browser/cortex/init",
16
+ async ({ model = DEFAULT_MODEL, onProgress }, { dispatch: dispatch2, rejectWithValue }) => {
31
17
  if (typeof window === "undefined") {
32
- throw new Error("BrowserCortex requires a browser environment");
18
+ return rejectWithValue("BrowserCortex requires a browser environment");
33
19
  }
34
- const { CreateWebWorkerMLCEngine } = await import("@mlc-ai/web-llm");
35
- const initProgressCallback = (report) => {
36
- const match = report.text?.match(/(\d+)%/);
37
- if (onProgress && match) onProgress(parseInt(match[1], 10));
38
- };
39
- const engine = await CreateWebWorkerMLCEngine(
40
- new Worker(new URL("./worker.js", import.meta.url), { type: "module" }),
41
- modelId,
42
- { initProgressCallback }
43
- );
44
- _state = {
45
- engine,
46
- status: {
20
+ const modelId = resolveModelId(model);
21
+ dispatch2(cortexInitStart());
22
+ dispatch2(setDownloadState({ isDownloading: true, progress: 0 }));
23
+ try {
24
+ const { CreateWebWorkerMLCEngine } = await import("@mlc-ai/web-llm");
25
+ const initProgressCallback = (report) => {
26
+ const match = report.text?.match(/(\d+)%/);
27
+ if (match) {
28
+ const progress = parseInt(match[1], 10);
29
+ dispatch2(setDownloadProgress(progress));
30
+ if (onProgress) onProgress(progress);
31
+ }
32
+ };
33
+ const engine = await CreateWebWorkerMLCEngine(
34
+ new Worker(new URL("./worker.js", import.meta.url), { type: "module" }),
35
+ modelId,
36
+ { initProgressCallback }
37
+ );
38
+ _engines[model] = engine;
39
+ const sessionInfo = {
40
+ id: model,
41
+ ready: true,
42
+ engine: "web-llm"
43
+ };
44
+ const newStatus = {
47
45
  id: `ctx_web_${Date.now()}`,
48
46
  model: modelId,
49
47
  ready: true,
50
48
  engine: "web-llm"
51
- }
52
- };
53
- return _state.status;
54
- };
55
- const ensureReady = async () => {
56
- if (!_state.status.ready) await init();
57
- return _state;
58
- };
59
- const complete = async (prompt, options = {}) => {
60
- const { engine } = await ensureReady();
61
- const reply = await engine.chat.completions.create({
62
- messages: [{ role: "user", content: prompt }],
63
- max_gen_len: options.maxTokens,
64
- temperature: options.temperature
65
- });
66
- return reply.choices[0].message.content || "";
67
- };
68
- const completeStream = async function* (prompt, options = {}) {
69
- const { engine } = await ensureReady();
70
- const chunks = await engine.chat.completions.create({
71
- messages: [{ role: "user", content: prompt }],
72
- max_gen_len: options.maxTokens,
73
- temperature: options.temperature,
74
- stream: true
49
+ };
50
+ dispatch2(cortexInitSuccess(newStatus));
51
+ dispatch2(setDownloadState({ isDownloading: false, progress: 100 }));
52
+ return sessionInfo;
53
+ } catch (e) {
54
+ const error = e.message || "Unknown error";
55
+ dispatch2(cortexInitFailed(error));
56
+ dispatch2(setDownloadState({ isDownloading: false, progress: 0 }));
57
+ return rejectWithValue(error);
58
+ }
59
+ }
60
+ );
61
+ var completeBrowserCortexThunk = createAsyncThunk(
62
+ "browser/cortex/complete",
63
+ async ({ model = DEFAULT_MODEL, prompt, maxTokens, temperature }, { rejectWithValue }) => {
64
+ const engine = _engines[model];
65
+ if (!engine) {
66
+ return rejectWithValue(`Browser Cortex session not found for model: ${model}`);
67
+ }
68
+ try {
69
+ const reply = await engine.chat.completions.create({
70
+ messages: [{ role: "user", content: prompt }],
71
+ max_gen_len: maxTokens,
72
+ temperature
73
+ });
74
+ return reply.choices[0].message.content || "";
75
+ } catch (e) {
76
+ return rejectWithValue(e.message || "Inference failed");
77
+ }
78
+ }
79
+ );
80
+ var browserCortexSlice = createSlice({
81
+ name: "browserCortex",
82
+ initialState: browserCortexAdapter.getInitialState({
83
+ progress: 0,
84
+ isDownloading: false,
85
+ error: null
86
+ }),
87
+ reducers: {
88
+ setDownloadProgress: (state, action) => {
89
+ state.progress = action.payload;
90
+ },
91
+ setDownloadState: (state, action) => {
92
+ state.isDownloading = action.payload.isDownloading;
93
+ state.progress = action.payload.progress;
94
+ }
95
+ },
96
+ extraReducers: (builder) => {
97
+ builder.addCase(initBrowserCortexThunk.fulfilled, (state, action) => {
98
+ browserCortexAdapter.upsertOne(state, action.payload);
99
+ state.error = null;
100
+ }).addCase(initBrowserCortexThunk.rejected, (state, action) => {
101
+ state.error = action.payload || "Init failed";
75
102
  });
76
- yield* yieldTokens(chunks[Symbol.asyncIterator]());
77
- };
78
- return { init, complete, completeStream };
79
- };
80
- var createCortex = (config = {}) => createBrowserCortex(config);
81
-
82
- // src/memory.ts
83
- import { memoiseAsync as memoiseAsync2 } from "@forbocai/core";
84
-
85
- // src/vector.ts
86
- import { memoiseAsync } from "@forbocai/core";
87
- var getEmbedder = memoiseAsync(async () => {
88
- if (typeof window === "undefined") {
89
- throw new Error("BrowserVectorEngine requires a browser environment");
90
103
  }
91
- const { pipeline } = await import("@huggingface/transformers");
92
- return pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
93
104
  });
94
- var initVectorEngine = async () => {
95
- await getEmbedder();
105
+ var { setDownloadProgress, setDownloadState } = browserCortexSlice.actions;
106
+ var {
107
+ selectById: selectBrowserCortexById,
108
+ selectAll: selectAllBrowserCortexSessions
109
+ } = browserCortexAdapter.getSelectors((state) => state.browserCortex);
110
+ var getBrowserEngine = (model = DEFAULT_MODEL) => _engines[model];
111
+ var browserCortexSlice_default = browserCortexSlice.reducer;
112
+
113
+ // src/browserMemorySlice.ts
114
+ import { createSlice as createSlice3, createAsyncThunk as createAsyncThunk3, createEntityAdapter as createEntityAdapter2 } from "@reduxjs/toolkit";
115
+ import { memoryStoreStart, memoryStoreSuccess, memoryStoreFailed, memoryRecallStart, memoryRecallSuccess, memoryRecallFailed, memoryClear } from "@forbocai/core";
116
+
117
+ // src/browserVectorSlice.ts
118
+ import { createSlice as createSlice2, createAsyncThunk as createAsyncThunk2 } from "@reduxjs/toolkit";
119
+ var _pipeline = null;
120
+ var initialState = {
121
+ isReady: false,
122
+ error: null
96
123
  };
124
+ var initBrowserVectorThunk = createAsyncThunk2(
125
+ "browser/vector/init",
126
+ async (_, { rejectWithValue }) => {
127
+ if (typeof window === "undefined") {
128
+ return rejectWithValue("BrowserVector requires a browser environment");
129
+ }
130
+ try {
131
+ const { pipeline } = await import("@huggingface/transformers");
132
+ _pipeline = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
133
+ } catch (e) {
134
+ return rejectWithValue(e.message || "Failed to load Transformers.js");
135
+ }
136
+ }
137
+ );
138
+ var generateBrowserEmbeddingThunk = createAsyncThunk2(
139
+ "browser/vector/generate",
140
+ async (text, { dispatch: dispatch2, rejectWithValue }) => {
141
+ if (!_pipeline) {
142
+ await dispatch2(initBrowserVectorThunk());
143
+ }
144
+ try {
145
+ const result = await _pipeline(text, { pooling: "mean", normalize: true });
146
+ return Array.from(result.data);
147
+ } catch (e) {
148
+ return rejectWithValue(e.message || "Embedding generation failed");
149
+ }
150
+ }
151
+ );
97
152
  var generateEmbedding = async (text) => {
98
- try {
99
- const embedder = await getEmbedder();
100
- const result = await embedder(text, { pooling: "mean", normalize: true });
101
- return Array.from(result.data);
102
- } catch (e) {
103
- console.error("Browser embedding failed:", e);
104
- return new Array(384).fill(0);
153
+ if (!_pipeline) {
154
+ const { pipeline } = await import("@huggingface/transformers");
155
+ _pipeline = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
105
156
  }
157
+ const result = await _pipeline(text, { pooling: "mean", normalize: true });
158
+ return Array.from(result.data);
106
159
  };
160
+ var browserVectorSlice = createSlice2({
161
+ name: "browserVector",
162
+ initialState,
163
+ reducers: {},
164
+ extraReducers: (builder) => {
165
+ builder.addCase(initBrowserVectorThunk.fulfilled, (state) => {
166
+ state.isReady = true;
167
+ state.error = null;
168
+ }).addCase(initBrowserVectorThunk.rejected, (state, action) => {
169
+ state.error = action.payload;
170
+ });
171
+ }
172
+ });
173
+ var browserVectorSlice_default = browserVectorSlice.reducer;
107
174
 
108
- // src/memory.ts
109
- var createBrowserMemory = (config = {}) => {
110
- const getDb = memoiseAsync2(async () => {
175
+ // src/browserMemorySlice.ts
176
+ var _dbs = {};
177
+ var browserMemoryAdapter = createEntityAdapter2();
178
+ var initBrowserMemoryThunk = createAsyncThunk3(
179
+ "browser/memory/init",
180
+ async ({ dbName = "default" } = {}, { rejectWithValue }) => {
111
181
  if (typeof window === "undefined") {
112
- throw new Error("BrowserMemory requires a browser environment");
182
+ return rejectWithValue("BrowserMemory requires a browser environment");
183
+ }
184
+ try {
185
+ const { create } = await import("@orama/orama");
186
+ const db = await create({
187
+ schema: {
188
+ id: "string",
189
+ text: "string",
190
+ timestamp: "number",
191
+ type: "string",
192
+ importance: "number",
193
+ embedding: "vector[384]"
194
+ }
195
+ });
196
+ _dbs[dbName] = db;
197
+ return { id: dbName, isInitialized: true };
198
+ } catch (e) {
199
+ return rejectWithValue(e.message || "Failed to initialize Orama");
200
+ }
201
+ }
202
+ );
203
+ var storeBrowserMemoryThunk = createAsyncThunk3(
204
+ "browser/memory/store",
205
+ async ({ text, type = "observation", importance = 0.5, dbName = "default" }, { dispatch: dispatch2, rejectWithValue }) => {
206
+ let db = _dbs[dbName];
207
+ if (!db) {
208
+ const result = await dispatch2(initBrowserMemoryThunk({ dbName })).unwrap();
209
+ db = _dbs[result.id];
210
+ }
211
+ dispatch2(memoryStoreStart());
212
+ try {
213
+ const item = {
214
+ id: `mem_br_${Date.now()}_${Math.random().toString(36).substring(7)}`,
215
+ text,
216
+ timestamp: Date.now(),
217
+ type,
218
+ importance
219
+ };
220
+ const embedding = await generateEmbedding(text);
221
+ const { insert } = await import("@orama/orama");
222
+ await insert(db, { ...item, embedding });
223
+ dispatch2(memoryStoreSuccess(item));
224
+ return item;
225
+ } catch (e) {
226
+ const error = e.message || "Memory store failed";
227
+ dispatch2(memoryStoreFailed(error));
228
+ return rejectWithValue(error);
113
229
  }
114
- const { create } = await import("@orama/orama");
115
- return create({
116
- schema: {
117
- id: "string",
118
- text: "string",
119
- timestamp: "number",
120
- type: "string",
121
- importance: "number",
122
- embedding: "vector[384]"
123
- }
230
+ }
231
+ );
232
+ var recallBrowserMemoryThunk = createAsyncThunk3(
233
+ "browser/memory/recall",
234
+ async ({ query, limit = 5, threshold = 0.5, dbName = "default" }, { dispatch: dispatch2, rejectWithValue }) => {
235
+ let db = _dbs[dbName];
236
+ if (!db) {
237
+ const result = await dispatch2(initBrowserMemoryThunk({ dbName })).unwrap();
238
+ db = _dbs[result.id];
239
+ }
240
+ dispatch2(memoryRecallStart());
241
+ try {
242
+ const embedding = await generateEmbedding(query);
243
+ const { search } = await import("@orama/orama");
244
+ const results = await search(db, {
245
+ mode: "vector",
246
+ vector: { value: embedding, property: "embedding" },
247
+ similarity: threshold,
248
+ limit
249
+ });
250
+ const items = results.hits.map((hit) => hit.document);
251
+ dispatch2(memoryRecallSuccess(items));
252
+ return items;
253
+ } catch (e) {
254
+ const error = e.message || "Memory recall failed";
255
+ dispatch2(memoryRecallFailed(error));
256
+ return rejectWithValue(error);
257
+ }
258
+ }
259
+ );
260
+ var clearBrowserMemoryThunk = createAsyncThunk3(
261
+ "browser/memory/clear",
262
+ async ({ dbName = "default" } = {}, { dispatch: dispatch2 }) => {
263
+ delete _dbs[dbName];
264
+ dispatch2(memoryClear());
265
+ }
266
+ );
267
+ var browserMemorySlice = createSlice3({
268
+ name: "browserMemory",
269
+ initialState: browserMemoryAdapter.getInitialState({
270
+ error: null
271
+ }),
272
+ reducers: {},
273
+ extraReducers: (builder) => {
274
+ builder.addCase(initBrowserMemoryThunk.fulfilled, (state, action) => {
275
+ browserMemoryAdapter.upsertOne(state, action.payload);
276
+ state.error = null;
277
+ }).addCase(initBrowserMemoryThunk.rejected, (state, action) => {
278
+ state.error = action.payload;
124
279
  });
280
+ }
281
+ });
282
+ var {
283
+ selectById: selectBrowserDBById,
284
+ selectAll: selectAllBrowserDBs
285
+ } = browserMemoryAdapter.getSelectors((state) => state.browserMemory);
286
+ var browserMemorySlice_default = browserMemorySlice.reducer;
287
+
288
+ // src/store.ts
289
+ import { createSDKStore } from "@forbocai/core";
290
+ var createBrowserStore = () => {
291
+ return createSDKStore({
292
+ browserCortex: browserCortexSlice_default,
293
+ browserMemory: browserMemorySlice_default,
294
+ browserVector: browserVectorSlice_default
125
295
  });
126
- const store = async (text, type = "observation", importance = 0.5) => {
127
- const instance = await getDb();
128
- const item = {
129
- id: `mem_br_${Date.now()}_${Math.random().toString(36).substring(7)}`,
130
- text,
131
- timestamp: Date.now(),
132
- type,
133
- importance
134
- };
135
- const embedding = await generateEmbedding(text);
136
- const { insert } = await import("@orama/orama");
137
- await insert(instance, { ...item, embedding });
138
- return item;
139
- };
140
- const recall = async (query, limit = 5, threshold) => {
141
- const instance = await getDb();
142
- const embedding = await generateEmbedding(query);
143
- const { search } = await import("@orama/orama");
144
- const results = await search(instance, {
145
- mode: "vector",
146
- vector: { value: embedding, property: "embedding" },
147
- similarity: threshold ?? 0.5,
148
- limit
149
- });
150
- return results.hits.map((hit) => hit.document);
151
- };
152
- const list = async (limit = 50, offset = 0) => {
153
- const instance = await getDb();
154
- const { search } = await import("@orama/orama");
155
- const results = await search(instance, { term: "", limit: limit + offset });
156
- return results.hits.slice(offset).map((hit) => hit.document);
157
- };
158
- const clear = async () => {
159
- };
160
- const exportMemories = async () => list(1e4);
161
- const importMemories = async (memories) => {
162
- await Promise.all(memories.map((m) => store(m.text, m.type, m.importance)));
163
- };
164
- return { store, recall, list, clear, export: exportMemories, import: importMemories };
165
296
  };
166
- var createMemory = (config = {}) => createBrowserMemory(config);
297
+ var store = createBrowserStore();
298
+ var dispatch = store.dispatch;
167
299
  export {
168
- createBrowserCortex,
169
- createBrowserMemory,
170
- createCortex,
171
- createMemory,
300
+ browserCortexSlice,
301
+ browserMemorySlice,
302
+ browserVectorSlice,
303
+ clearBrowserMemoryThunk,
304
+ completeBrowserCortexThunk,
305
+ createBrowserStore,
306
+ dispatch,
307
+ generateBrowserEmbeddingThunk,
172
308
  generateEmbedding,
173
- initVectorEngine
309
+ getBrowserEngine,
310
+ initBrowserCortexThunk,
311
+ initBrowserMemoryThunk,
312
+ initBrowserVectorThunk,
313
+ recallBrowserMemoryThunk,
314
+ selectAllBrowserCortexSessions,
315
+ selectAllBrowserDBs,
316
+ selectBrowserCortexById,
317
+ selectBrowserDBById,
318
+ setDownloadProgress,
319
+ setDownloadState,
320
+ store,
321
+ storeBrowserMemoryThunk
174
322
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forbocai/browser",
3
- "version": "0.5.9",
3
+ "version": "0.6.1",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Web Browser implementation for ForbocAI SDK",
6
6
  "main": "dist/index.js",
@@ -12,10 +12,11 @@
12
12
  "test": "vitest"
13
13
  },
14
14
  "dependencies": {
15
- "@forbocai/core": "^0.5.9",
15
+ "@forbocai/core": "^0.6.1",
16
+ "@huggingface/transformers": "^3.0.0",
16
17
  "@mlc-ai/web-llm": "^0.2.46",
17
18
  "@orama/orama": "^2.0.26",
18
- "@huggingface/transformers": "^3.0.0"
19
+ "@reduxjs/toolkit": "^2.11.2"
19
20
  },
20
21
  "devDependencies": {
21
22
  "tsup": "^8.5.1",