brevit 0.1.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/LICENSE +23 -0
- package/README.md +1237 -0
- package/TYPESCRIPT.md +230 -0
- package/example.ts +139 -0
- package/package.json +31 -0
- package/src/brevit.d.ts +212 -0
- package/src/brevit.js +589 -0
- package/test/test.js +90 -0
- package/tsconfig.json +23 -0
package/src/brevit.js
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* =================================================================================
|
|
3
|
+
* BREVIT.JS (brevit-js)
|
|
4
|
+
*
|
|
5
|
+
* A high-performance JavaScript library for semantically compressing
|
|
6
|
+
* and optimizing data before sending it to a Large Language Model (LLM).
|
|
7
|
+
*
|
|
8
|
+
* Project: Brevit
|
|
9
|
+
* Author: Javian
|
|
10
|
+
* Version: 0.1.0
|
|
11
|
+
* =================================================================================
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Define enums for configuration (using a JS object)
|
|
15
|
+
export const JsonOptimizationMode = {
|
|
16
|
+
None: 'None',
|
|
17
|
+
Flatten: 'Flatten',
|
|
18
|
+
ToYaml: 'ToYaml', // Note: Requires a YAML library like 'js-yaml'
|
|
19
|
+
Filter: 'Filter', // Note: Requires a JSON-path library or custom logic
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const TextOptimizationMode = {
|
|
23
|
+
None: 'None',
|
|
24
|
+
Clean: 'Clean',
|
|
25
|
+
SummarizeFast: 'SummarizeFast',
|
|
26
|
+
SummarizeHighQuality: 'SummarizeHighQuality',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const ImageOptimizationMode = {
|
|
30
|
+
None: 'None',
|
|
31
|
+
Ocr: 'Ocr',
|
|
32
|
+
Metadata: 'Metadata',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configuration object for the BrevitClient.
|
|
37
|
+
*/
|
|
38
|
+
export class BrevitConfig {
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} options
|
|
41
|
+
* @param {string} options.jsonMode - Strategy for JSON optimization.
|
|
42
|
+
* @param {string} options.textMode - Strategy for Text optimization.
|
|
43
|
+
* @param {string} options.imageMode - Strategy for Image optimization.
|
|
44
|
+
* @param {string[]} options.jsonPathsToKeep - Paths to keep for Filter mode.
|
|
45
|
+
* @param {number} options.longTextThreshold - Char count to trigger text optimization.
|
|
46
|
+
*/
|
|
47
|
+
constructor({
|
|
48
|
+
jsonMode = JsonOptimizationMode.Flatten,
|
|
49
|
+
textMode = TextOptimizationMode.Clean,
|
|
50
|
+
imageMode = ImageOptimizationMode.Ocr,
|
|
51
|
+
jsonPathsToKeep = [],
|
|
52
|
+
longTextThreshold = 500,
|
|
53
|
+
} = {}) {
|
|
54
|
+
this.jsonMode = jsonMode;
|
|
55
|
+
this.textMode = textMode;
|
|
56
|
+
this.imageMode = imageMode;
|
|
57
|
+
this.jsonPathsToKeep = jsonPathsToKeep;
|
|
58
|
+
this.longTextThreshold = longTextThreshold;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The main client for the Brevit.js library.
|
|
64
|
+
* This class orchestrates the optimization pipeline.
|
|
65
|
+
*/
|
|
66
|
+
export class BrevitClient {
|
|
67
|
+
/**
|
|
68
|
+
* @param {BrevitConfig} config
|
|
69
|
+
* @param {Object} options - Optional custom optimizers
|
|
70
|
+
* @param {Function} options.textOptimizer - Custom text optimizer function
|
|
71
|
+
* @param {Function} options.imageOptimizer - Custom image optimizer function
|
|
72
|
+
*/
|
|
73
|
+
constructor(config = new BrevitConfig(), options = {}) {
|
|
74
|
+
this._config = config;
|
|
75
|
+
this._textOptimizer = options.textOptimizer || this._defaultTextOptimizer.bind(this);
|
|
76
|
+
this._imageOptimizer = options.imageOptimizer || this._defaultImageOptimizer.bind(this);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Checks if an array contains uniform objects (all have same keys).
|
|
81
|
+
* @param {Array} arr - The array to check
|
|
82
|
+
* @returns {Object|null} Object with keys array if uniform, null otherwise
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
_isUniformObjectArray(arr) {
|
|
86
|
+
if (!Array.isArray(arr) || arr.length === 0) return null;
|
|
87
|
+
|
|
88
|
+
const firstItem = arr[0];
|
|
89
|
+
if (typeof firstItem !== 'object' || firstItem === null || Array.isArray(firstItem)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Preserve original field order instead of sorting
|
|
94
|
+
const firstKeys = Object.keys(firstItem);
|
|
95
|
+
const firstKeySet = new Set(firstKeys);
|
|
96
|
+
|
|
97
|
+
// Check if all items have the same keys (order-independent)
|
|
98
|
+
for (let i = 1; i < arr.length; i++) {
|
|
99
|
+
const item = arr[i];
|
|
100
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const itemKeys = Object.keys(item);
|
|
104
|
+
if (firstKeys.length !== itemKeys.length) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
// Check if all keys exist (order doesn't matter for uniformity)
|
|
108
|
+
if (!itemKeys.every(key => firstKeySet.has(key))) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { keys: firstKeys };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Checks if an array contains only primitives of compatible types.
|
|
118
|
+
* @param {Array} arr - The array to check
|
|
119
|
+
* @returns {boolean} True if all elements are primitives
|
|
120
|
+
* @private
|
|
121
|
+
*/
|
|
122
|
+
_isPrimitiveArray(arr) {
|
|
123
|
+
if (!Array.isArray(arr) || arr.length === 0) return false;
|
|
124
|
+
|
|
125
|
+
const firstType = typeof arr[0];
|
|
126
|
+
if (firstType === 'object' && arr[0] !== null) return false;
|
|
127
|
+
|
|
128
|
+
// Check if all elements are primitives
|
|
129
|
+
for (let i = 1; i < arr.length; i++) {
|
|
130
|
+
const itemType = typeof arr[i];
|
|
131
|
+
if (itemType === 'object' && arr[i] !== null) return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Escapes a value for comma-separated format.
|
|
139
|
+
* @param {any} value - The value to escape
|
|
140
|
+
* @returns {string} Escaped string
|
|
141
|
+
* @private
|
|
142
|
+
*/
|
|
143
|
+
_escapeValue(value) {
|
|
144
|
+
const str = String(value);
|
|
145
|
+
// Quote if contains comma, newline, or quotes
|
|
146
|
+
if (str.includes(',') || str.includes('\n') || str.includes('"')) {
|
|
147
|
+
return `"${str.replace(/"/g, '\\"')}"`;
|
|
148
|
+
}
|
|
149
|
+
return str;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Formats a uniform object array in tabular format.
|
|
154
|
+
* @param {Array} arr - The uniform object array
|
|
155
|
+
* @param {string} prefix - The key path prefix
|
|
156
|
+
* @returns {string} Formatted tabular string
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
_formatTabularArray(arr, prefix) {
|
|
160
|
+
const { keys } = this._isUniformObjectArray(arr);
|
|
161
|
+
const header = `${prefix}[${arr.length}]{${keys.join(',')}}:`;
|
|
162
|
+
const rows = arr.map(item =>
|
|
163
|
+
keys.map(key => this._escapeValue(item[key] ?? 'null')).join(',')
|
|
164
|
+
);
|
|
165
|
+
return `${header}\n${rows.join('\n')}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Formats a primitive array in comma-separated format.
|
|
170
|
+
* @param {Array} arr - The primitive array
|
|
171
|
+
* @param {string} prefix - The key path prefix
|
|
172
|
+
* @returns {string} Formatted comma-separated string
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
175
|
+
_formatPrimitiveArray(arr, prefix) {
|
|
176
|
+
const values = arr.map(item => this._escapeValue(item));
|
|
177
|
+
return `${prefix}[${arr.length}]:${values.join(',')}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Recursive helper for flattening a JSON object/array with tabular optimization.
|
|
182
|
+
* @param {any} node - The current JS object, array, or value.
|
|
183
|
+
* @param {string} prefix - The key path built so far.
|
|
184
|
+
* @param {Array<string>} output - The output array of formatted lines.
|
|
185
|
+
* @private
|
|
186
|
+
*/
|
|
187
|
+
_flatten(node, prefix = '', output = []) {
|
|
188
|
+
if (typeof node === 'object' && node !== null && !Array.isArray(node)) {
|
|
189
|
+
// It's an object
|
|
190
|
+
Object.entries(node).forEach(([key, value]) => {
|
|
191
|
+
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
192
|
+
this._flatten(value, newPrefix, output);
|
|
193
|
+
});
|
|
194
|
+
} else if (Array.isArray(node)) {
|
|
195
|
+
// It's an array - check for optimization opportunities
|
|
196
|
+
|
|
197
|
+
// Check for uniform object array (tabular format)
|
|
198
|
+
const uniformCheck = this._isUniformObjectArray(node);
|
|
199
|
+
if (uniformCheck) {
|
|
200
|
+
output.push(this._formatTabularArray(node, prefix));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check for primitive array (comma-separated format)
|
|
205
|
+
if (this._isPrimitiveArray(node)) {
|
|
206
|
+
output.push(this._formatPrimitiveArray(node, prefix));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Fall back to current format for mixed/non-uniform arrays
|
|
211
|
+
node.forEach((item, index) => {
|
|
212
|
+
const newPrefix = `${prefix}[${index}]`;
|
|
213
|
+
this._flatten(item, newPrefix, output);
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
// It's a primitive value (string, number, boolean, null)
|
|
217
|
+
if (!prefix) prefix = 'value'; // Handle root-level value
|
|
218
|
+
output.push(`${prefix}:${String(node)}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Flattens a JS object into a token-efficient string with tabular optimization.
|
|
224
|
+
* @param {object} obj - The object to flatten.
|
|
225
|
+
* @returns {string} The flattened string.
|
|
226
|
+
* @private
|
|
227
|
+
*/
|
|
228
|
+
_flattenObject(obj) {
|
|
229
|
+
const output = [];
|
|
230
|
+
this._flatten(obj, '', output);
|
|
231
|
+
return output.join('\n');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Analyzes data structure to determine the best optimization strategy.
|
|
236
|
+
* @param {any} data - The data to analyze
|
|
237
|
+
* @returns {Object} Analysis result with recommended strategy
|
|
238
|
+
* @private
|
|
239
|
+
*/
|
|
240
|
+
_analyzeDataStructure(data) {
|
|
241
|
+
const analysis = {
|
|
242
|
+
type: null,
|
|
243
|
+
depth: 0,
|
|
244
|
+
hasUniformArrays: false,
|
|
245
|
+
hasPrimitiveArrays: false,
|
|
246
|
+
hasNestedObjects: false,
|
|
247
|
+
textLength: 0,
|
|
248
|
+
arrayCount: 0,
|
|
249
|
+
objectCount: 0,
|
|
250
|
+
complexity: 'simple'
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const analyze = (node, depth = 0, path = '') => {
|
|
254
|
+
analysis.depth = Math.max(analysis.depth, depth);
|
|
255
|
+
|
|
256
|
+
if (typeof node === 'string') {
|
|
257
|
+
analysis.textLength += node.length;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (Array.isArray(node)) {
|
|
262
|
+
analysis.arrayCount++;
|
|
263
|
+
|
|
264
|
+
// Check for uniform object arrays
|
|
265
|
+
const uniformCheck = this._isUniformObjectArray(node);
|
|
266
|
+
if (uniformCheck) {
|
|
267
|
+
analysis.hasUniformArrays = true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check for primitive arrays
|
|
271
|
+
if (this._isPrimitiveArray(node)) {
|
|
272
|
+
analysis.hasPrimitiveArrays = true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Analyze each element
|
|
276
|
+
node.forEach((item, index) => {
|
|
277
|
+
analyze(item, depth + 1, `${path}[${index}]`);
|
|
278
|
+
});
|
|
279
|
+
} else if (typeof node === 'object' && node !== null) {
|
|
280
|
+
analysis.objectCount++;
|
|
281
|
+
|
|
282
|
+
if (depth > 0) {
|
|
283
|
+
analysis.hasNestedObjects = true;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
Object.entries(node).forEach(([key, value]) => {
|
|
287
|
+
analyze(value, depth + 1, path ? `${path}.${key}` : key);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
analyze(data);
|
|
293
|
+
|
|
294
|
+
// Determine complexity
|
|
295
|
+
if (analysis.depth > 3 || analysis.arrayCount > 5 || analysis.objectCount > 10) {
|
|
296
|
+
analysis.complexity = 'complex';
|
|
297
|
+
} else if (analysis.depth > 1 || analysis.arrayCount > 0 || analysis.objectCount > 3) {
|
|
298
|
+
analysis.complexity = 'moderate';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Determine type
|
|
302
|
+
if (typeof data === 'string') {
|
|
303
|
+
analysis.type = data.length > this._config.longTextThreshold ? 'longText' : 'text';
|
|
304
|
+
} else if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
|
|
305
|
+
analysis.type = 'image';
|
|
306
|
+
} else if (Array.isArray(data)) {
|
|
307
|
+
analysis.type = 'array';
|
|
308
|
+
} else if (typeof data === 'object' && data !== null) {
|
|
309
|
+
analysis.type = 'object';
|
|
310
|
+
} else {
|
|
311
|
+
analysis.type = 'primitive';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return analysis;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Selects the best optimization strategy based on data analysis.
|
|
319
|
+
* @param {Object} analysis - Data structure analysis
|
|
320
|
+
* @returns {Object} Strategy configuration
|
|
321
|
+
* @private
|
|
322
|
+
*/
|
|
323
|
+
_selectOptimalStrategy(analysis) {
|
|
324
|
+
// Strategy scoring: higher score = better fit
|
|
325
|
+
const strategies = [];
|
|
326
|
+
|
|
327
|
+
// Strategy 1: Flatten with tabular optimization (best for uniform arrays)
|
|
328
|
+
if (analysis.hasUniformArrays || analysis.hasPrimitiveArrays) {
|
|
329
|
+
strategies.push({
|
|
330
|
+
name: 'Flatten',
|
|
331
|
+
jsonMode: JsonOptimizationMode.Flatten,
|
|
332
|
+
score: analysis.hasUniformArrays ? 100 : 80,
|
|
333
|
+
reason: analysis.hasUniformArrays
|
|
334
|
+
? 'Uniform object arrays detected - tabular format optimal'
|
|
335
|
+
: 'Primitive arrays detected - comma-separated format optimal'
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Strategy 2: Standard flatten (good for nested objects)
|
|
340
|
+
if (analysis.hasNestedObjects || analysis.complexity === 'moderate') {
|
|
341
|
+
strategies.push({
|
|
342
|
+
name: 'Flatten',
|
|
343
|
+
jsonMode: JsonOptimizationMode.Flatten,
|
|
344
|
+
score: 70,
|
|
345
|
+
reason: 'Nested objects detected - flatten format optimal'
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Strategy 3: YAML (good for readable structures)
|
|
350
|
+
if (analysis.complexity === 'moderate' && !analysis.hasUniformArrays) {
|
|
351
|
+
strategies.push({
|
|
352
|
+
name: 'ToYaml',
|
|
353
|
+
jsonMode: JsonOptimizationMode.ToYaml,
|
|
354
|
+
score: 60,
|
|
355
|
+
reason: 'Moderate complexity - YAML format may be more readable'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Strategy 4: Text optimization (for long text)
|
|
360
|
+
if (analysis.type === 'longText') {
|
|
361
|
+
strategies.push({
|
|
362
|
+
name: 'TextOptimization',
|
|
363
|
+
textMode: this._config.textMode,
|
|
364
|
+
score: 90,
|
|
365
|
+
reason: 'Long text detected - summarization recommended'
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Strategy 5: Image optimization (for image data)
|
|
370
|
+
if (analysis.type === 'image') {
|
|
371
|
+
strategies.push({
|
|
372
|
+
name: 'ImageOptimization',
|
|
373
|
+
imageMode: this._config.imageMode,
|
|
374
|
+
score: 100,
|
|
375
|
+
reason: 'Image data detected - OCR recommended'
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Select highest scoring strategy
|
|
380
|
+
if (strategies.length === 0) {
|
|
381
|
+
return {
|
|
382
|
+
name: 'Flatten',
|
|
383
|
+
jsonMode: JsonOptimizationMode.Flatten,
|
|
384
|
+
score: 50,
|
|
385
|
+
reason: 'Default flatten strategy'
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return strategies.reduce((best, current) =>
|
|
390
|
+
current.score > best.score ? current : best
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Intelligently optimizes data by automatically selecting the best strategy.
|
|
396
|
+
* This method analyzes the input data structure and applies the most
|
|
397
|
+
* appropriate optimization methods automatically.
|
|
398
|
+
*
|
|
399
|
+
* @param {any} rawData - The data to optimize (object, JSON string, text, ArrayBuffer).
|
|
400
|
+
* @param {string} [intent] - (Optional) A hint about the user's goal.
|
|
401
|
+
* @returns {Promise<string>} A promise that resolves to the optimized string.
|
|
402
|
+
*/
|
|
403
|
+
async brevity(rawData, intent = null) {
|
|
404
|
+
// Normalize input to object for analysis
|
|
405
|
+
let inputObject = null;
|
|
406
|
+
let inputType = typeof rawData;
|
|
407
|
+
|
|
408
|
+
if (inputType === 'string') {
|
|
409
|
+
try {
|
|
410
|
+
const trimmed = rawData.trim();
|
|
411
|
+
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
412
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
413
|
+
inputObject = JSON.parse(rawData);
|
|
414
|
+
} else {
|
|
415
|
+
// It's plain text - analyze and optimize
|
|
416
|
+
const analysis = this._analyzeDataStructure(rawData);
|
|
417
|
+
const strategy = this._selectOptimalStrategy(analysis);
|
|
418
|
+
|
|
419
|
+
if (strategy.name === 'TextOptimization') {
|
|
420
|
+
return await this._textOptimizer(rawData, intent);
|
|
421
|
+
}
|
|
422
|
+
return rawData;
|
|
423
|
+
}
|
|
424
|
+
} catch (e) {
|
|
425
|
+
// Not JSON - treat as text
|
|
426
|
+
const analysis = this._analyzeDataStructure(rawData);
|
|
427
|
+
const strategy = this._selectOptimalStrategy(analysis);
|
|
428
|
+
|
|
429
|
+
if (strategy.name === 'TextOptimization') {
|
|
430
|
+
return await this._textOptimizer(rawData, intent);
|
|
431
|
+
}
|
|
432
|
+
return rawData;
|
|
433
|
+
}
|
|
434
|
+
} else if (inputType === 'object' && rawData !== null) {
|
|
435
|
+
// Check if it's image data
|
|
436
|
+
if (rawData instanceof ArrayBuffer ||
|
|
437
|
+
rawData instanceof Uint8Array ||
|
|
438
|
+
(rawData.constructor && rawData.constructor.name === 'Buffer')) {
|
|
439
|
+
return await this._imageOptimizer(rawData, intent);
|
|
440
|
+
}
|
|
441
|
+
inputObject = rawData;
|
|
442
|
+
} else {
|
|
443
|
+
// Primitive - return as-is
|
|
444
|
+
return String(rawData);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Analyze the data structure
|
|
448
|
+
const analysis = this._analyzeDataStructure(inputObject);
|
|
449
|
+
const strategy = this._selectOptimalStrategy(analysis);
|
|
450
|
+
|
|
451
|
+
// Apply the selected strategy
|
|
452
|
+
const tempConfig = new BrevitConfig({
|
|
453
|
+
...this._config,
|
|
454
|
+
jsonMode: strategy.jsonMode || this._config.jsonMode,
|
|
455
|
+
textMode: strategy.textMode || this._config.textMode,
|
|
456
|
+
imageMode: strategy.imageMode || this._config.imageMode
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Temporarily override config for this optimization
|
|
460
|
+
const originalConfig = this._config;
|
|
461
|
+
this._config = tempConfig;
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
// Use the existing optimize method with the selected strategy
|
|
465
|
+
return await this.optimize(inputObject, intent);
|
|
466
|
+
} finally {
|
|
467
|
+
// Restore original config
|
|
468
|
+
this._config = originalConfig;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Registers a custom optimization strategy for the brevity method.
|
|
474
|
+
* This allows extending Brevit with new optimization strategies.
|
|
475
|
+
*
|
|
476
|
+
* @param {string} name - Strategy name
|
|
477
|
+
* @param {Function} analyzer - Function that analyzes data and returns score (0-100)
|
|
478
|
+
* @param {Function} optimizer - Function that optimizes the data
|
|
479
|
+
* @example
|
|
480
|
+
* brevit.registerStrategy('custom', (data) => ({ score: 85, reason: 'Custom logic' }), async (data) => { ... });
|
|
481
|
+
*/
|
|
482
|
+
registerStrategy(name, analyzer, optimizer) {
|
|
483
|
+
if (!this._strategies) {
|
|
484
|
+
this._strategies = new Map();
|
|
485
|
+
}
|
|
486
|
+
this._strategies.set(name, { analyzer, optimizer });
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* The primary method. Optimizes any JS object, JSON string,
|
|
491
|
+
* or text into a token-efficient string.
|
|
492
|
+
*
|
|
493
|
+
* @param {any} rawData - The data to optimize (object, JSON string, text, ArrayBuffer).
|
|
494
|
+
* @param {string} [intent] - (Optional) A hint about the user's goal.
|
|
495
|
+
* @returns {Promise<string>} A promise that resolves to the optimized string.
|
|
496
|
+
*/
|
|
497
|
+
async optimize(rawData, intent = null) {
|
|
498
|
+
let inputObject = null;
|
|
499
|
+
let inputType = typeof rawData;
|
|
500
|
+
|
|
501
|
+
if (inputType === 'string') {
|
|
502
|
+
// Could be JSON string or just text
|
|
503
|
+
try {
|
|
504
|
+
const trimmed = rawData.trim();
|
|
505
|
+
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
506
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
507
|
+
inputObject = JSON.parse(rawData);
|
|
508
|
+
}
|
|
509
|
+
} catch (e) {
|
|
510
|
+
// It's not a JSON string, treat as text
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (!inputObject) {
|
|
514
|
+
// It's text
|
|
515
|
+
if (rawData.length > this._config.longTextThreshold) {
|
|
516
|
+
// It's long text, apply text optimization
|
|
517
|
+
return await this._textOptimizer(rawData, intent);
|
|
518
|
+
}
|
|
519
|
+
// It's short text, return as-is
|
|
520
|
+
return rawData;
|
|
521
|
+
}
|
|
522
|
+
} else if (inputType === 'object' && rawData !== null) {
|
|
523
|
+
// Check if it's an ArrayBuffer or TypedArray (image data)
|
|
524
|
+
if (rawData instanceof ArrayBuffer ||
|
|
525
|
+
rawData instanceof Uint8Array ||
|
|
526
|
+
(rawData.constructor && rawData.constructor.name === 'Buffer')) {
|
|
527
|
+
return await this._imageOptimizer(rawData, intent);
|
|
528
|
+
}
|
|
529
|
+
// It's a plain JS object
|
|
530
|
+
inputObject = rawData;
|
|
531
|
+
} else {
|
|
532
|
+
// Other primitives, return as-is
|
|
533
|
+
return String(rawData);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// If we're here, we have an object (from JSON or POJO)
|
|
537
|
+
// Now apply the configured JSON optimization
|
|
538
|
+
switch (this._config.jsonMode) {
|
|
539
|
+
case JsonOptimizationMode.Flatten:
|
|
540
|
+
return this._flattenObject(inputObject);
|
|
541
|
+
|
|
542
|
+
case JsonOptimizationMode.ToYaml:
|
|
543
|
+
// STUB: Requires a 'js-yaml' library
|
|
544
|
+
// import YAML from 'js-yaml';
|
|
545
|
+
// return YAML.dump(inputObject);
|
|
546
|
+
console.warn('[Brevit] ToYaml mode requires installing a YAML library (e.g., js-yaml).');
|
|
547
|
+
return JSON.stringify(inputObject, null, 2); // Fallback
|
|
548
|
+
|
|
549
|
+
case JsonOptimizationMode.Filter:
|
|
550
|
+
// STUB: Requires a JSON-path library
|
|
551
|
+
console.warn('[Brevit] Filter mode is not implemented in this stub.');
|
|
552
|
+
return JSON.stringify(inputObject); // Fallback
|
|
553
|
+
|
|
554
|
+
case JsonOptimizationMode.None:
|
|
555
|
+
default:
|
|
556
|
+
return JSON.stringify(inputObject); // Return as unformatted JSON
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Default text optimizer (STUB).
|
|
562
|
+
* In a frontend, this would likely make an API call to a
|
|
563
|
+
* backend (C# or Python) that runs Semantic Kernel or LangChain
|
|
564
|
+
* to do the actual summarization.
|
|
565
|
+
* @private
|
|
566
|
+
*/
|
|
567
|
+
async _defaultTextOptimizer(longText, intent) {
|
|
568
|
+
// STUB: A real frontend app would call its backend for this.
|
|
569
|
+
// NEVER put LLM API keys in a frontend app.
|
|
570
|
+
console.warn('[Brevit] Text summarization should be done on a secure backend.');
|
|
571
|
+
const mode = this._config.textMode;
|
|
572
|
+
const stubSummary = longText.substring(0, 150);
|
|
573
|
+
return `[${mode} Stub: Summary of text follows...]\n${stubSummary}...\n[End of summary]`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Default image optimizer (STUB).
|
|
578
|
+
* In a frontend, this would likely make an API call to a
|
|
579
|
+
* backend that runs OCR services.
|
|
580
|
+
* @private
|
|
581
|
+
*/
|
|
582
|
+
async _defaultImageOptimizer(imageData, intent) {
|
|
583
|
+
// STUB: A real frontend app would call its backend for this.
|
|
584
|
+
console.warn('[Brevit] Image OCR should be done on a secure backend.');
|
|
585
|
+
const size = imageData instanceof ArrayBuffer ? imageData.byteLength : imageData.length;
|
|
586
|
+
return `[OCR Stub: Extracted text from image (${size} bytes)]\nSample OCR Text: INVOICE #1234\nTotal: $499.99\n[End of extracted text]`;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
package/test/test.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { BrevitClient, BrevitConfig, JsonOptimizationMode } from '../src/brevit.js';
|
|
2
|
+
|
|
3
|
+
async function runTests() {
|
|
4
|
+
console.log('Running Brevit.js Tests...\n');
|
|
5
|
+
|
|
6
|
+
let passed = 0;
|
|
7
|
+
let failed = 0;
|
|
8
|
+
|
|
9
|
+
function test(name, fn) {
|
|
10
|
+
try {
|
|
11
|
+
fn();
|
|
12
|
+
console.log(`✓ ${name}`);
|
|
13
|
+
passed++;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`✗ ${name}: ${error.message}`);
|
|
16
|
+
failed++;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Test 1: Flatten JSON object
|
|
21
|
+
test('Flatten JSON object', async () => {
|
|
22
|
+
const config = new BrevitConfig({ jsonMode: JsonOptimizationMode.Flatten });
|
|
23
|
+
const brevit = new BrevitClient(config);
|
|
24
|
+
|
|
25
|
+
const testObject = {
|
|
26
|
+
user: {
|
|
27
|
+
name: 'Javian',
|
|
28
|
+
email: 'support@javianpicardo.com'
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const result = await brevit.optimize(testObject);
|
|
33
|
+
if (!result.includes('user.name: Javian') || !result.includes('user.email: support@javianpicardo.com')) {
|
|
34
|
+
throw new Error('Flattened output does not contain expected values');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Test 2: Flatten JSON string
|
|
39
|
+
test('Flatten JSON string', async () => {
|
|
40
|
+
const config = new BrevitConfig({ jsonMode: JsonOptimizationMode.Flatten });
|
|
41
|
+
const brevit = new BrevitClient(config);
|
|
42
|
+
|
|
43
|
+
const jsonString = '{"order": {"orderId": "o-456", "status": "SHIPPED"}}';
|
|
44
|
+
const result = await brevit.optimize(jsonString);
|
|
45
|
+
|
|
46
|
+
if (!result.includes('order.orderId: o-456') || !result.includes('order.status: SHIPPED')) {
|
|
47
|
+
throw new Error('Flattened output does not contain expected values');
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Test 3: Short text returns as-is
|
|
52
|
+
test('Short text returns as-is', async () => {
|
|
53
|
+
const config = new BrevitConfig({ longTextThreshold: 500 });
|
|
54
|
+
const brevit = new BrevitClient(config);
|
|
55
|
+
|
|
56
|
+
const shortText = 'Hello World';
|
|
57
|
+
const result = await brevit.optimize(shortText);
|
|
58
|
+
|
|
59
|
+
if (result !== 'Hello World') {
|
|
60
|
+
throw new Error('Short text was modified');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Test 4: Array handling
|
|
65
|
+
test('Array handling', async () => {
|
|
66
|
+
const config = new BrevitConfig({ jsonMode: JsonOptimizationMode.Flatten });
|
|
67
|
+
const brevit = new BrevitClient(config);
|
|
68
|
+
|
|
69
|
+
const testObject = {
|
|
70
|
+
items: [
|
|
71
|
+
{ sku: 'A-88', name: 'Brevit Pro' },
|
|
72
|
+
{ sku: 'T-22', name: 'Toon Handbook' }
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = await brevit.optimize(testObject);
|
|
77
|
+
if (!result.includes('items[0].sku: A-88') || !result.includes('items[1].sku: T-22')) {
|
|
78
|
+
throw new Error('Array flattening failed');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log(`\nTests completed: ${passed} passed, ${failed} failed`);
|
|
83
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
runTests().catch(error => {
|
|
87
|
+
console.error('Test runner error:', error);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
90
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"strict": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"noEmit": false
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist", "test"]
|
|
22
|
+
}
|
|
23
|
+
|