semantic-state-estimator 1.0.0

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.js ADDED
@@ -0,0 +1,202 @@
1
+ // src/math/vector.ts
2
+ function assertSameDimension(a, b) {
3
+ if (a.length !== b.length) {
4
+ throw new Error(
5
+ `Vector dimension mismatch: a=${a.length}, b=${b.length}`
6
+ );
7
+ }
8
+ }
9
+ function add(a, b) {
10
+ assertSameDimension(a, b);
11
+ return a.map((val, i) => val + b[i]);
12
+ }
13
+ function scale(v, scalar) {
14
+ return v.map((val) => val * scalar);
15
+ }
16
+ function normalize(v) {
17
+ const mag = Math.sqrt(v.reduce((sum, val) => sum + val * val, 0));
18
+ if (mag === 0) {
19
+ return v.map(() => 0);
20
+ }
21
+ return v.map((val) => val / mag);
22
+ }
23
+ function cosineSimilarity(a, b) {
24
+ assertSameDimension(a, b);
25
+ const dot = a.reduce((sum, val, i) => sum + val * b[i], 0);
26
+ const magA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
27
+ const magB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
28
+ if (magA === 0 || magB === 0) {
29
+ return 0;
30
+ }
31
+ return dot / (magA * magB);
32
+ }
33
+ function emaFusion(current, previous, alpha) {
34
+ assertSameDimension(current, previous);
35
+ if (alpha <= 0 || alpha > 1) {
36
+ throw new Error(`Alpha must be in the range (0, 1], got ${alpha}`);
37
+ }
38
+ return current.map((val, i) => alpha * val + (1 - alpha) * previous[i]);
39
+ }
40
+
41
+ // src/engine/SemanticStateEngine.ts
42
+ var AGE_DECAY_RATE = 1e-4;
43
+ var DRIFT_WEIGHT = 0.5;
44
+ var SemanticStateEngine = class {
45
+ constructor(config) {
46
+ this.listeners = /* @__PURE__ */ new Set();
47
+ this.alpha = config.alpha;
48
+ this.driftThreshold = config.driftThreshold;
49
+ this.onDriftDetected = config.onDriftDetected;
50
+ this.provider = config.provider;
51
+ this.modelName = config.modelName ?? "Xenova/all-MiniLM-L6-v2";
52
+ this.stateVector = [];
53
+ this.lastUpdatedAt = Date.now();
54
+ this.lastDrift = 0;
55
+ this.updateCount = 0;
56
+ }
57
+ /**
58
+ * Obtains an embedding for `text` from the WorkerManager and fuses it into
59
+ * the rolling semantic state using EMA.
60
+ *
61
+ * On the first call the embedding establishes the baseline.
62
+ * On subsequent calls, if the cosine similarity between the current state
63
+ * and the new embedding falls below {@link SemanticStateEngineConfig.driftThreshold},
64
+ * the {@link SemanticStateEngineConfig.onDriftDetected} callback is fired
65
+ * *before* the EMA fusion is applied.
66
+ *
67
+ * @param text Raw text whose embedding will be fused into the state.
68
+ */
69
+ async update(text) {
70
+ const raw = await this.provider.getEmbedding(text);
71
+ if (raw === null) {
72
+ return;
73
+ }
74
+ const embedding = Array.from(raw);
75
+ if (this.updateCount === 0) {
76
+ const zero = new Array(embedding.length).fill(0);
77
+ this.stateVector = emaFusion(embedding, zero, this.alpha);
78
+ this.lastDrift = 0;
79
+ } else {
80
+ if (embedding.length !== this.stateVector.length) {
81
+ throw new Error(
82
+ `Embedding dimension mismatch: expected ${this.stateVector.length}, got ${embedding.length}`
83
+ );
84
+ }
85
+ const similarity = cosineSimilarity(this.stateVector, embedding);
86
+ const drift = 1 - similarity;
87
+ if (similarity < this.driftThreshold) {
88
+ this.onDriftDetected?.(embedding, drift);
89
+ }
90
+ this.stateVector = emaFusion(embedding, this.stateVector, this.alpha);
91
+ this.lastDrift = drift;
92
+ }
93
+ this.lastUpdatedAt = Date.now();
94
+ this.updateCount++;
95
+ this.listeners.forEach((l) => l());
96
+ }
97
+ /**
98
+ * Subscribes to state changes. Returns an unsubscribe function.
99
+ * The listener is called after every successful `update`.
100
+ */
101
+ subscribe(listener) {
102
+ this.listeners.add(listener);
103
+ return () => this.listeners.delete(listener);
104
+ }
105
+ /**
106
+ * Returns a point-in-time snapshot of the current semantic state.
107
+ */
108
+ getSnapshot() {
109
+ const healthScore = this.calculateHealth();
110
+ return {
111
+ vector: [...this.stateVector],
112
+ healthScore,
113
+ timestamp: this.lastUpdatedAt,
114
+ semanticSummary: this.buildSummary(healthScore)
115
+ };
116
+ }
117
+ /**
118
+ * Computes the current healthScore.
119
+ *
120
+ * Starts at 1.0 and subtracts:
121
+ * - An age penalty proportional to milliseconds elapsed since the last update.
122
+ * - A drift penalty proportional to the most recent drift magnitude.
123
+ *
124
+ * The result is clamped to [0, 1].
125
+ */
126
+ calculateHealth() {
127
+ const timeSinceUpdate = Date.now() - this.lastUpdatedAt;
128
+ const agePenalty = timeSinceUpdate * AGE_DECAY_RATE;
129
+ const driftPenalty = this.lastDrift * DRIFT_WEIGHT;
130
+ return Math.max(0, Math.min(1, 1 - agePenalty - driftPenalty));
131
+ }
132
+ buildSummary(healthScore) {
133
+ if (healthScore > 0.8) return "stable";
134
+ if (healthScore > 0.5) return "drifting";
135
+ return "volatile";
136
+ }
137
+ };
138
+
139
+ // src/worker/WorkerManager.ts
140
+ var WorkerManager = class {
141
+ constructor(workerUrl = new URL("./embedding.worker.js", import.meta.url), modelName = "Xenova/all-MiniLM-L6-v2") {
142
+ this.pendingRequests = /* @__PURE__ */ new Map();
143
+ this.isReady = false;
144
+ this.worker = new Worker(workerUrl, { type: "module" });
145
+ this.worker.onmessage = (event) => {
146
+ const data = event.data;
147
+ switch (data.type) {
148
+ case "STATUS":
149
+ this.isReady = data.status === "ready";
150
+ return;
151
+ case "PROGRESS":
152
+ return;
153
+ case "EMBED_RES": {
154
+ const { id, vector, error } = data;
155
+ const pending = this.pendingRequests.get(id);
156
+ if (!pending) return;
157
+ this.pendingRequests.delete(id);
158
+ if (error !== void 0) {
159
+ pending.reject(new Error(error));
160
+ } else if (vector !== null) {
161
+ pending.resolve(vector);
162
+ } else {
163
+ pending.reject(new Error("Worker returned null vector without an error message"));
164
+ }
165
+ return;
166
+ }
167
+ }
168
+ };
169
+ const initMessage = { type: "INIT", modelName };
170
+ this.worker.postMessage(initMessage);
171
+ }
172
+ /**
173
+ * Sends `text` to the worker and returns a Promise that resolves with the
174
+ * resulting embedding vector, or rejects if the worker reports an error.
175
+ *
176
+ * If the worker is not yet ready (model still loading), the request is
177
+ * silently dropped and the Promise resolves with `null` to avoid
178
+ * unhandled promise rejections during the initial page load.
179
+ */
180
+ getEmbedding(text) {
181
+ if (!this.isReady) {
182
+ console.warn("SemanticStateEngine: Worker still loading, dropping early event.");
183
+ return Promise.resolve(null);
184
+ }
185
+ return new Promise((resolve, reject) => {
186
+ const id = crypto.randomUUID();
187
+ this.pendingRequests.set(id, { resolve, reject });
188
+ const request = { type: "EMBED", id, text };
189
+ this.worker.postMessage(request);
190
+ });
191
+ }
192
+ };
193
+ export {
194
+ SemanticStateEngine,
195
+ WorkerManager,
196
+ add,
197
+ cosineSimilarity,
198
+ emaFusion,
199
+ normalize,
200
+ scale
201
+ };
202
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/math/vector.ts","../src/engine/SemanticStateEngine.ts","../src/worker/WorkerManager.ts"],"sourcesContent":["/**\n * Pure vector math utilities for semantic state estimation.\n *\n * Provides vector addition, scalar multiplication, normalization,\n * cosine similarity, and EMA (Exponential Moving Average) fusion.\n */\n\n/** Asserts that two vectors have the same length, throwing otherwise. */\nfunction assertSameDimension(a: number[], b: number[]): void {\n if (a.length !== b.length) {\n throw new Error(\n `Vector dimension mismatch: a=${a.length}, b=${b.length}`,\n );\n }\n}\n\n/**\n * Adds two vectors element-wise.\n *\n * @param a First vector\n * @param b Second vector\n * @returns Element-wise sum\n */\nexport function add(a: number[], b: number[]): number[] {\n assertSameDimension(a, b);\n return a.map((val, i) => val + b[i]!);\n}\n\n/**\n * Multiplies every element of a vector by a scalar.\n *\n * @param v Input vector\n * @param scalar Scalar multiplier\n * @returns Scaled vector\n */\nexport function scale(v: number[], scalar: number): number[] {\n return v.map((val) => val * scalar);\n}\n\n/**\n * Normalizes a vector to unit length (L2 normalization).\n *\n * @param v Input vector\n * @returns Unit vector, or zero vector if input magnitude is 0\n */\nexport function normalize(v: number[]): number[] {\n const mag = Math.sqrt(v.reduce((sum, val) => sum + val * val, 0));\n if (mag === 0) {\n return v.map(() => 0);\n }\n return v.map((val) => val / mag);\n}\n\n/**\n * Computes the cosine similarity between two vectors.\n *\n * @param a First vector\n * @param b Second vector\n * @returns Cosine similarity in [-1, 1], or 0 if either vector has zero magnitude\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n assertSameDimension(a, b);\n const dot = a.reduce((sum, val, i) => sum + val * b[i]!, 0);\n const magA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));\n const magB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));\n if (magA === 0 || magB === 0) {\n return 0;\n }\n return dot / (magA * magB);\n}\n\n/**\n * Computes the Exponential Moving Average (EMA) fusion of two vectors.\n *\n * Formula: S_t = α · E_t + (1 − α) · S_{t-1}\n *\n * @param current New embedding vector E_t\n * @param previous Previous state vector S_{t-1}\n * @param alpha Decay factor α ∈ (0, 1]. Higher values weight recent events more.\n * @returns Updated state vector S_t\n */\nexport function emaFusion(\n current: number[],\n previous: number[],\n alpha: number,\n): number[] {\n assertSameDimension(current, previous);\n if (alpha <= 0 || alpha > 1) {\n throw new Error(`Alpha must be in the range (0, 1], got ${alpha}`);\n }\n return current.map((val, i) => alpha * val + (1 - alpha) * previous[i]!);\n}\n","import { emaFusion, cosineSimilarity } from \"../math/vector.js\";\n\n/**\n * A generic embedding provider contract.\n * Any object that can return an embedding vector for a given text satisfies this interface.\n * This includes `WorkerManager` (on-device WebWorker) as well as custom OpenAI, Ollama,\n * or other remote-inference wrappers.\n */\nexport interface EmbeddingProvider {\n getEmbedding(text: string): Promise<Float32Array | number[]>;\n}\n\n/**\n * Age-based health decay rate: health lost per millisecond of inactivity.\n * At this rate, age alone reduces health to 0 after ~10 seconds of inactivity.\n */\nconst AGE_DECAY_RATE = 0.0001;\n\n/**\n * Weight applied to the most-recent drift value when computing healthScore.\n * A drift of 1.0 (orthogonal vectors) reduces health by 0.5.\n */\nconst DRIFT_WEIGHT = 0.5;\n\n/**\n * Configuration for the SemanticStateEngine.\n */\nexport interface SemanticStateEngineConfig {\n /** EMA decay factor α ∈ (0, 1]. Higher values weight recent embeddings more. */\n alpha: number;\n\n /** Minimum cosine similarity below which drift is detected and the callback fires. */\n driftThreshold: number;\n\n /**\n * Optional callback invoked when the incoming embedding drifts beyond the threshold.\n * Fired *before* the EMA fusion is applied.\n *\n * @param vector The incoming embedding that triggered the drift.\n * @param driftScore Drift magnitude: 1 − cosine_similarity ∈ [0, 2].\n */\n onDriftDetected?: (vector: number[], driftScore: number) => void;\n /**\n * The embedding provider used to obtain embedding vectors asynchronously.\n * Any object implementing `getEmbedding(text: string): Promise<Float32Array | number[]>`\n * satisfies this interface — including `WorkerManager`, or a custom OpenAI / Ollama wrapper.\n */\n provider: EmbeddingProvider;\n\n /**\n * The name of the embedding model to use.\n * Must match the modelName passed to the WorkerManager so the worker\n * loads the correct model.\n * @default \"Xenova/all-MiniLM-L6-v2\"\n */\n modelName?: string;\n}\n\n/**\n * A point-in-time snapshot of the current semantic state.\n */\nexport interface Snapshot {\n /** The current EMA state vector. */\n vector: number[];\n\n /** Reliability indicator in [0, 1]. Degrades with age and high drift. */\n healthScore: number;\n\n /** Unix timestamp (ms) of the last state update. */\n timestamp: number;\n\n /** Human-readable description of the current state quality. */\n semanticSummary: string;\n}\n\n/**\n * SemanticStateEngine tracks the implicit semantic intent of an event stream\n * using Exponential Moving Average (EMA) vector fusion.\n *\n * It fires an optional drift callback when incoming embeddings diverge\n * significantly from the current state, and exposes a healthScore that\n * degrades with both age and volatility.\n */\nexport class SemanticStateEngine {\n private readonly alpha: number;\n private readonly driftThreshold: number;\n private readonly onDriftDetected?: (\n vector: number[],\n driftScore: number,\n ) => void;\n private readonly provider: EmbeddingProvider;\n readonly modelName: string;\n\n private stateVector: number[];\n private lastUpdatedAt: number;\n private lastDrift: number;\n private updateCount: number;\n private readonly listeners = new Set<() => void>();\n\n constructor(config: SemanticStateEngineConfig) {\n this.alpha = config.alpha;\n this.driftThreshold = config.driftThreshold;\n this.onDriftDetected = config.onDriftDetected;\n this.provider = config.provider;\n this.modelName = config.modelName ?? \"Xenova/all-MiniLM-L6-v2\";\n\n this.stateVector = [];\n this.lastUpdatedAt = Date.now();\n this.lastDrift = 0;\n this.updateCount = 0;\n }\n\n /**\n * Obtains an embedding for `text` from the WorkerManager and fuses it into\n * the rolling semantic state using EMA.\n *\n * On the first call the embedding establishes the baseline.\n * On subsequent calls, if the cosine similarity between the current state\n * and the new embedding falls below {@link SemanticStateEngineConfig.driftThreshold},\n * the {@link SemanticStateEngineConfig.onDriftDetected} callback is fired\n * *before* the EMA fusion is applied.\n *\n * @param text Raw text whose embedding will be fused into the state.\n */\n async update(text: string): Promise<void> {\n const raw = await this.provider.getEmbedding(text);\n if (raw === null) {\n return;\n }\n const embedding = Array.from(raw);\n\n if (this.updateCount === 0) {\n // First call: establish baseline from a zero-vector origin.\n const zero = new Array(embedding.length).fill(0) as number[];\n this.stateVector = emaFusion(embedding, zero, this.alpha);\n this.lastDrift = 0;\n } else {\n if (embedding.length !== this.stateVector.length) {\n throw new Error(\n `Embedding dimension mismatch: expected ${this.stateVector.length}, got ${embedding.length}`,\n );\n }\n\n const similarity = cosineSimilarity(this.stateVector, embedding);\n const drift = 1 - similarity;\n\n if (similarity < this.driftThreshold) {\n this.onDriftDetected?.(embedding, drift);\n }\n\n this.stateVector = emaFusion(embedding, this.stateVector, this.alpha);\n this.lastDrift = drift;\n }\n\n this.lastUpdatedAt = Date.now();\n this.updateCount++;\n this.listeners.forEach((l) => l());\n }\n\n /**\n * Subscribes to state changes. Returns an unsubscribe function.\n * The listener is called after every successful `update`.\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns a point-in-time snapshot of the current semantic state.\n */\n getSnapshot(): Snapshot {\n const healthScore = this.calculateHealth();\n return {\n vector: [...this.stateVector],\n healthScore,\n timestamp: this.lastUpdatedAt,\n semanticSummary: this.buildSummary(healthScore),\n };\n }\n\n /**\n * Computes the current healthScore.\n *\n * Starts at 1.0 and subtracts:\n * - An age penalty proportional to milliseconds elapsed since the last update.\n * - A drift penalty proportional to the most recent drift magnitude.\n *\n * The result is clamped to [0, 1].\n */\n private calculateHealth(): number {\n const timeSinceUpdate = Date.now() - this.lastUpdatedAt;\n const agePenalty = timeSinceUpdate * AGE_DECAY_RATE;\n const driftPenalty = this.lastDrift * DRIFT_WEIGHT;\n return Math.max(0, Math.min(1, 1.0 - agePenalty - driftPenalty));\n }\n\n private buildSummary(healthScore: number): string {\n if (healthScore > 0.8) return \"stable\";\n if (healthScore > 0.5) return \"drifting\";\n return \"volatile\";\n }\n}\n","import type { EmbeddingRequest, EmbeddingResponse, WorkerInitMessage, WorkerStatusEvent } from \"./types.js\";\n\ntype PendingRequest = {\n resolve: (value: Float32Array) => void;\n reject: (reason: Error) => void;\n};\n\n/**\n * WorkerManager wraps a browser Worker in a clean async/await API.\n *\n * It maintains a Map of pending Promises keyed by request UUID so that\n * out-of-order worker responses are still dispatched to the correct caller.\n *\n * Calls to `getEmbedding()` while the worker is not yet ready are silently\n * dropped (returning null) to avoid unhandled promise rejections during the\n * model loading phase, which is acceptable for probabilistic semantic state.\n */\nexport class WorkerManager {\n private readonly worker: Worker;\n private readonly pendingRequests = new Map<string, PendingRequest>();\n private isReady: boolean = false;\n\n constructor(\n workerUrl: string | URL = new URL(\"./embedding.worker.js\", import.meta.url),\n modelName: string = \"Xenova/all-MiniLM-L6-v2\",\n ) {\n this.worker = new Worker(workerUrl, { type: \"module\" });\n this.worker.onmessage = (event: MessageEvent<EmbeddingResponse | WorkerStatusEvent>) => {\n const data = event.data;\n switch (data.type) {\n case 'STATUS':\n this.isReady = data.status === 'ready';\n return;\n case 'PROGRESS':\n return;\n case 'EMBED_RES': {\n const { id, vector, error } = data;\n const pending = this.pendingRequests.get(id);\n if (!pending) return;\n this.pendingRequests.delete(id);\n if (error !== undefined) {\n pending.reject(new Error(error));\n } else if (vector !== null) {\n pending.resolve(vector);\n } else {\n pending.reject(new Error(\"Worker returned null vector without an error message\"));\n }\n return;\n }\n }\n };\n\n const initMessage: WorkerInitMessage = { type: \"INIT\", modelName };\n this.worker.postMessage(initMessage);\n }\n\n /**\n * Sends `text` to the worker and returns a Promise that resolves with the\n * resulting embedding vector, or rejects if the worker reports an error.\n *\n * If the worker is not yet ready (model still loading), the request is\n * silently dropped and the Promise resolves with `null` to avoid\n * unhandled promise rejections during the initial page load.\n */\n getEmbedding(text: string): Promise<Float32Array | null> {\n if (!this.isReady) {\n console.warn(\"SemanticStateEngine: Worker still loading, dropping early event.\");\n return Promise.resolve(null);\n }\n return new Promise<Float32Array>((resolve, reject) => {\n const id = crypto.randomUUID();\n this.pendingRequests.set(id, { resolve, reject });\n const request: EmbeddingRequest = { type: \"EMBED\", id, text };\n this.worker.postMessage(request);\n });\n }\n}\n"],"mappings":"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","names":[]}
package/dist/react.cjs ADDED
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ useSemanticState: () => useSemanticState
24
+ });
25
+ module.exports = __toCommonJS(react_exports);
26
+
27
+ // src/react/useSemanticState.ts
28
+ var import_react = require("react");
29
+ var useSemanticState = (engine) => {
30
+ return (0, import_react.useSyncExternalStore)(
31
+ (listener) => engine.subscribe(listener),
32
+ () => engine.getSnapshot()
33
+ );
34
+ };
35
+ // Annotate the CommonJS export names for ESM import in node:
36
+ 0 && (module.exports = {
37
+ useSemanticState
38
+ });
39
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/index.ts","../src/react/useSemanticState.ts"],"sourcesContent":["export { useSemanticState } from \"./useSemanticState.js\";\n","import { useSyncExternalStore } from \"react\";\nimport type { SemanticStateEngine } from \"../engine/SemanticStateEngine.js\";\n\n/**\n * React hook that subscribes to a `SemanticStateEngine` and returns a\n * point-in-time snapshot of its state.\n *\n * Built on `useSyncExternalStore` so it is safe for concurrent React and\n * produces no tearing. The component re-renders automatically whenever the\n * engine fires a state-change notification via `subscribe`.\n */\nexport const useSemanticState = (engine: SemanticStateEngine) => {\n return useSyncExternalStore(\n (listener) => engine.subscribe(listener),\n () => engine.getSnapshot(),\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAqC;AAW9B,IAAM,mBAAmB,CAAC,WAAgC;AAC/D,aAAO;AAAA,IACL,CAAC,aAAa,OAAO,UAAU,QAAQ;AAAA,IACvC,MAAM,OAAO,YAAY;AAAA,EAC3B;AACF;","names":[]}
@@ -0,0 +1,13 @@
1
+ import { S as SemanticStateEngine, b as Snapshot } from './SemanticStateEngine-BP5URJOJ.cjs';
2
+
3
+ /**
4
+ * React hook that subscribes to a `SemanticStateEngine` and returns a
5
+ * point-in-time snapshot of its state.
6
+ *
7
+ * Built on `useSyncExternalStore` so it is safe for concurrent React and
8
+ * produces no tearing. The component re-renders automatically whenever the
9
+ * engine fires a state-change notification via `subscribe`.
10
+ */
11
+ declare const useSemanticState: (engine: SemanticStateEngine) => Snapshot;
12
+
13
+ export { useSemanticState };
@@ -0,0 +1,13 @@
1
+ import { S as SemanticStateEngine, b as Snapshot } from './SemanticStateEngine-BP5URJOJ.js';
2
+
3
+ /**
4
+ * React hook that subscribes to a `SemanticStateEngine` and returns a
5
+ * point-in-time snapshot of its state.
6
+ *
7
+ * Built on `useSyncExternalStore` so it is safe for concurrent React and
8
+ * produces no tearing. The component re-renders automatically whenever the
9
+ * engine fires a state-change notification via `subscribe`.
10
+ */
11
+ declare const useSemanticState: (engine: SemanticStateEngine) => Snapshot;
12
+
13
+ export { useSemanticState };
package/dist/react.js ADDED
@@ -0,0 +1,12 @@
1
+ // src/react/useSemanticState.ts
2
+ import { useSyncExternalStore } from "react";
3
+ var useSemanticState = (engine) => {
4
+ return useSyncExternalStore(
5
+ (listener) => engine.subscribe(listener),
6
+ () => engine.getSnapshot()
7
+ );
8
+ };
9
+ export {
10
+ useSemanticState
11
+ };
12
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/useSemanticState.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\nimport type { SemanticStateEngine } from \"../engine/SemanticStateEngine.js\";\n\n/**\n * React hook that subscribes to a `SemanticStateEngine` and returns a\n * point-in-time snapshot of its state.\n *\n * Built on `useSyncExternalStore` so it is safe for concurrent React and\n * produces no tearing. The component re-renders automatically whenever the\n * engine fires a state-change notification via `subscribe`.\n */\nexport const useSemanticState = (engine: SemanticStateEngine) => {\n return useSyncExternalStore(\n (listener) => engine.subscribe(listener),\n () => engine.getSnapshot(),\n );\n};\n"],"mappings":";AAAA,SAAS,4BAA4B;AAW9B,IAAM,mBAAmB,CAAC,WAAgC;AAC/D,SAAO;AAAA,IACL,CAAC,aAAa,OAAO,UAAU,QAAQ;AAAA,IACvC,MAAM,OAAO,YAAY;AAAA,EAC3B;AACF;","names":[]}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/adapters/zustand.ts
21
+ var zustand_exports = {};
22
+ __export(zustand_exports, {
23
+ semanticMiddleware: () => semanticMiddleware
24
+ });
25
+ module.exports = __toCommonJS(zustand_exports);
26
+ var semanticMiddleware = (engine, mapper, config, onError = console.error) => (set, get, api) => config(
27
+ (partial, replace) => {
28
+ const prevState = get();
29
+ set(partial, replace);
30
+ const nextState = get();
31
+ const semanticText = mapper(nextState, prevState);
32
+ if (semanticText) {
33
+ engine.update(semanticText).catch(onError);
34
+ }
35
+ },
36
+ get,
37
+ api
38
+ );
39
+ // Annotate the CommonJS export names for ESM import in node:
40
+ 0 && (module.exports = {
41
+ semanticMiddleware
42
+ });
43
+ //# sourceMappingURL=zustand.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/zustand.ts"],"sourcesContent":["import type { StateCreator } from \"zustand\";\nimport type { SemanticStateEngine } from \"../engine/SemanticStateEngine.js\";\n\n/**\n * Maps a Zustand state transition to a semantic string for the AI engine.\n * Return `null` to skip the update (lazy evaluation / noise filtering).\n */\nexport type SemanticMapper<T> = (\n state: T,\n previousState: T,\n) => string | null | undefined;\n\n/**\n * Zustand middleware that intercepts state changes and fires the semantic\n * engine asynchronously in the background (fire-and-forget).\n *\n * The `set` call is synchronous and unblocked; the engine runs in the\n * WebWorker so no frames are dropped.\n *\n * @param onError Optional callback invoked when `engine.update` rejects.\n * Defaults to `console.error`.\n */\nexport const semanticMiddleware =\n <T>(\n engine: SemanticStateEngine,\n mapper: SemanticMapper<T>,\n config: StateCreator<T>,\n onError: (error: unknown) => void = console.error,\n ): StateCreator<T> =>\n (set, get, api) =>\n config(\n (partial, replace) => {\n const prevState = get();\n // Let Zustand perform the normal synchronous update first.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n set(partial as any, replace as any);\n const nextState = get();\n\n // Map the state change to a semantic string.\n const semanticText = mapper(nextState, prevState);\n\n // If valid, fire-and-forget to the WebWorker engine.\n if (semanticText) {\n engine.update(semanticText).catch(onError);\n }\n },\n get,\n api,\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBO,IAAM,qBACX,CACE,QACA,QACA,QACA,UAAoC,QAAQ,UAE9C,CAAC,KAAK,KAAK,QACT;AAAA,EACE,CAAC,SAAS,YAAY;AACpB,UAAM,YAAY,IAAI;AAGtB,QAAI,SAAgB,OAAc;AAClC,UAAM,YAAY,IAAI;AAGtB,UAAM,eAAe,OAAO,WAAW,SAAS;AAGhD,QAAI,cAAc;AAChB,aAAO,OAAO,YAAY,EAAE,MAAM,OAAO;AAAA,IAC3C;AAAA,EACF;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -0,0 +1,21 @@
1
+ import { StateCreator } from 'zustand';
2
+ import { S as SemanticStateEngine } from './SemanticStateEngine-BP5URJOJ.cjs';
3
+
4
+ /**
5
+ * Maps a Zustand state transition to a semantic string for the AI engine.
6
+ * Return `null` to skip the update (lazy evaluation / noise filtering).
7
+ */
8
+ type SemanticMapper<T> = (state: T, previousState: T) => string | null | undefined;
9
+ /**
10
+ * Zustand middleware that intercepts state changes and fires the semantic
11
+ * engine asynchronously in the background (fire-and-forget).
12
+ *
13
+ * The `set` call is synchronous and unblocked; the engine runs in the
14
+ * WebWorker so no frames are dropped.
15
+ *
16
+ * @param onError Optional callback invoked when `engine.update` rejects.
17
+ * Defaults to `console.error`.
18
+ */
19
+ declare const semanticMiddleware: <T>(engine: SemanticStateEngine, mapper: SemanticMapper<T>, config: StateCreator<T>, onError?: (error: unknown) => void) => StateCreator<T>;
20
+
21
+ export { type SemanticMapper, semanticMiddleware };
@@ -0,0 +1,21 @@
1
+ import { StateCreator } from 'zustand';
2
+ import { S as SemanticStateEngine } from './SemanticStateEngine-BP5URJOJ.js';
3
+
4
+ /**
5
+ * Maps a Zustand state transition to a semantic string for the AI engine.
6
+ * Return `null` to skip the update (lazy evaluation / noise filtering).
7
+ */
8
+ type SemanticMapper<T> = (state: T, previousState: T) => string | null | undefined;
9
+ /**
10
+ * Zustand middleware that intercepts state changes and fires the semantic
11
+ * engine asynchronously in the background (fire-and-forget).
12
+ *
13
+ * The `set` call is synchronous and unblocked; the engine runs in the
14
+ * WebWorker so no frames are dropped.
15
+ *
16
+ * @param onError Optional callback invoked when `engine.update` rejects.
17
+ * Defaults to `console.error`.
18
+ */
19
+ declare const semanticMiddleware: <T>(engine: SemanticStateEngine, mapper: SemanticMapper<T>, config: StateCreator<T>, onError?: (error: unknown) => void) => StateCreator<T>;
20
+
21
+ export { type SemanticMapper, semanticMiddleware };
@@ -0,0 +1,18 @@
1
+ // src/adapters/zustand.ts
2
+ var semanticMiddleware = (engine, mapper, config, onError = console.error) => (set, get, api) => config(
3
+ (partial, replace) => {
4
+ const prevState = get();
5
+ set(partial, replace);
6
+ const nextState = get();
7
+ const semanticText = mapper(nextState, prevState);
8
+ if (semanticText) {
9
+ engine.update(semanticText).catch(onError);
10
+ }
11
+ },
12
+ get,
13
+ api
14
+ );
15
+ export {
16
+ semanticMiddleware
17
+ };
18
+ //# sourceMappingURL=zustand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/zustand.ts"],"sourcesContent":["import type { StateCreator } from \"zustand\";\nimport type { SemanticStateEngine } from \"../engine/SemanticStateEngine.js\";\n\n/**\n * Maps a Zustand state transition to a semantic string for the AI engine.\n * Return `null` to skip the update (lazy evaluation / noise filtering).\n */\nexport type SemanticMapper<T> = (\n state: T,\n previousState: T,\n) => string | null | undefined;\n\n/**\n * Zustand middleware that intercepts state changes and fires the semantic\n * engine asynchronously in the background (fire-and-forget).\n *\n * The `set` call is synchronous and unblocked; the engine runs in the\n * WebWorker so no frames are dropped.\n *\n * @param onError Optional callback invoked when `engine.update` rejects.\n * Defaults to `console.error`.\n */\nexport const semanticMiddleware =\n <T>(\n engine: SemanticStateEngine,\n mapper: SemanticMapper<T>,\n config: StateCreator<T>,\n onError: (error: unknown) => void = console.error,\n ): StateCreator<T> =>\n (set, get, api) =>\n config(\n (partial, replace) => {\n const prevState = get();\n // Let Zustand perform the normal synchronous update first.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n set(partial as any, replace as any);\n const nextState = get();\n\n // Map the state change to a semantic string.\n const semanticText = mapper(nextState, prevState);\n\n // If valid, fire-and-forget to the WebWorker engine.\n if (semanticText) {\n engine.update(semanticText).catch(onError);\n }\n },\n get,\n api,\n );\n"],"mappings":";AAsBO,IAAM,qBACX,CACE,QACA,QACA,QACA,UAAoC,QAAQ,UAE9C,CAAC,KAAK,KAAK,QACT;AAAA,EACE,CAAC,SAAS,YAAY;AACpB,UAAM,YAAY,IAAI;AAGtB,QAAI,SAAgB,OAAc;AAClC,UAAM,YAAY,IAAI;AAGtB,UAAM,eAAe,OAAO,WAAW,SAAS;AAGhD,QAAI,cAAc;AAChB,aAAO,OAAO,YAAY,EAAE,MAAM,OAAO;AAAA,IAC3C;AAAA,EACF;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "semantic-state-estimator",
3
+ "version": "1.0.0",
4
+ "description": "A TypeScript library that tracks the implicit semantic intent, emotional state, or 'vibe' of a user/system using local text embeddings and EMA fusion.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./zustand": {
16
+ "types": "./dist/zustand.d.ts",
17
+ "import": "./dist/zustand.js",
18
+ "require": "./dist/zustand.cjs"
19
+ },
20
+ "./react": {
21
+ "types": "./dist/react.d.ts",
22
+ "import": "./dist/react.js",
23
+ "require": "./dist/react.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "lint": "tsc --noEmit",
36
+ "prepublishOnly": "npm run lint && npm run test && npm run build",
37
+ "release": "bash scripts/publish.sh"
38
+ },
39
+ "keywords": [
40
+ "semantic",
41
+ "state",
42
+ "embeddings",
43
+ "ema",
44
+ "webworker",
45
+ "onnx",
46
+ "typescript"
47
+ ],
48
+ "sideEffects": false,
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "author": "Francisco Rueda",
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/franruedaesq/semantic-state-estimator.git"
57
+ },
58
+ "homepage": "https://github.com/franruedaesq/semantic-state-estimator#readme",
59
+ "bugs": {
60
+ "url": "https://github.com/franruedaesq/semantic-state-estimator/issues"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "devDependencies": {
66
+ "@testing-library/react": "^16.3.2",
67
+ "@types/react": "^19.2.14",
68
+ "@types/react-dom": "^19.2.3",
69
+ "jsdom": "^26.1.0",
70
+ "tsup": "^8.4.0",
71
+ "typescript": "^5.8.2",
72
+ "vitest": "^3.0.8"
73
+ },
74
+ "dependencies": {
75
+ "@huggingface/transformers": "^3.8.1"
76
+ },
77
+ "peerDependencies": {
78
+ "react": ">=18",
79
+ "react-dom": ">=18",
80
+ "zustand": ">=4"
81
+ },
82
+ "peerDependenciesMeta": {
83
+ "react": {
84
+ "optional": true
85
+ },
86
+ "react-dom": {
87
+ "optional": true
88
+ },
89
+ "zustand": {
90
+ "optional": true
91
+ }
92
+ }
93
+ }