@ruvector/edge-net 0.5.0 → 0.5.3
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/README.md +281 -10
- package/core-invariants.js +942 -0
- package/models/adapter-hub.js +1008 -0
- package/models/adapter-security.js +792 -0
- package/models/benchmark.js +688 -0
- package/models/distribution.js +791 -0
- package/models/index.js +109 -0
- package/models/integrity.js +753 -0
- package/models/loader.js +725 -0
- package/models/microlora.js +1298 -0
- package/models/model-loader.js +922 -0
- package/models/model-optimizer.js +1245 -0
- package/models/model-registry.js +696 -0
- package/models/model-utils.js +548 -0
- package/models/models-cli.js +914 -0
- package/models/registry.json +214 -0
- package/models/training-utils.js +1418 -0
- package/models/wasm-core.js +1025 -0
- package/network-genesis.js +2847 -0
- package/onnx-worker.js +462 -8
- package/package.json +33 -3
- package/plugins/SECURITY-AUDIT.md +654 -0
- package/plugins/cli.js +43 -3
- package/plugins/implementations/e2e-encryption.js +57 -12
- package/plugins/plugin-loader.js +610 -21
- package/tests/model-optimizer.test.js +644 -0
- package/tests/network-genesis.test.js +562 -0
- package/tests/plugin-benchmark.js +1239 -0
- package/tests/plugin-system-test.js +163 -0
- package/tests/wasm-core.test.js +368 -0
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdapterHub - Community Adapter Registry and Management
|
|
3
|
+
*
|
|
4
|
+
* Provides a marketplace-style interface for browsing, uploading,
|
|
5
|
+
* downloading, and applying community-created LoRA adapters.
|
|
6
|
+
*
|
|
7
|
+
* @module @ruvector/edge-net/models/adapter-hub
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```javascript
|
|
11
|
+
* import { AdapterHub } from '@ruvector/edge-net/models';
|
|
12
|
+
*
|
|
13
|
+
* const hub = new AdapterHub();
|
|
14
|
+
*
|
|
15
|
+
* // Browse adapters by category
|
|
16
|
+
* const codeAdapters = await hub.browse({ domain: 'code', sort: 'rating' });
|
|
17
|
+
*
|
|
18
|
+
* // Download and apply an adapter
|
|
19
|
+
* const adapter = await hub.download('popular-code-adapter-v1');
|
|
20
|
+
* await myLoRA.loadAdapter(adapter);
|
|
21
|
+
*
|
|
22
|
+
* // Upload your own adapter
|
|
23
|
+
* await hub.upload(myLoRA, {
|
|
24
|
+
* name: 'My Code Assistant',
|
|
25
|
+
* description: 'Fine-tuned for Python coding',
|
|
26
|
+
* domain: 'code',
|
|
27
|
+
* tags: ['python', 'coding', 'assistant']
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { EventEmitter } from 'events';
|
|
33
|
+
import { createHash, randomBytes } from 'crypto';
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// TYPE DEFINITIONS (JSDoc)
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} AdapterInfo
|
|
41
|
+
* @property {string} id - Unique adapter identifier
|
|
42
|
+
* @property {string} name - Human-readable name
|
|
43
|
+
* @property {string} description - Detailed description
|
|
44
|
+
* @property {string} author - Author name or ID
|
|
45
|
+
* @property {string} authorId - Unique author identifier
|
|
46
|
+
* @property {string} baseModel - Base model identifier
|
|
47
|
+
* @property {string} domain - Primary domain category
|
|
48
|
+
* @property {string[]} tags - Searchable tags
|
|
49
|
+
* @property {number} rating - Average rating (0-5)
|
|
50
|
+
* @property {number} ratingCount - Number of ratings
|
|
51
|
+
* @property {number} downloads - Total download count
|
|
52
|
+
* @property {number} size - Size in bytes
|
|
53
|
+
* @property {string} version - Adapter version
|
|
54
|
+
* @property {string} license - License identifier
|
|
55
|
+
* @property {number} createdAt - Creation timestamp
|
|
56
|
+
* @property {number} updatedAt - Last update timestamp
|
|
57
|
+
* @property {Object} config - LoRA configuration
|
|
58
|
+
* @property {Object} stats - Training statistics
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {Object} BrowseOptions
|
|
63
|
+
* @property {string} [domain] - Filter by domain
|
|
64
|
+
* @property {string} [baseModel] - Filter by base model
|
|
65
|
+
* @property {string} [query] - Search query
|
|
66
|
+
* @property {string[]} [tags] - Filter by tags
|
|
67
|
+
* @property {string} [sort='downloads'] - Sort order
|
|
68
|
+
* @property {number} [limit=20] - Results per page
|
|
69
|
+
* @property {number} [offset=0] - Pagination offset
|
|
70
|
+
* @property {number} [minRating=0] - Minimum rating filter
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {Object} UploadOptions
|
|
75
|
+
* @property {string} name - Adapter name
|
|
76
|
+
* @property {string} description - Adapter description
|
|
77
|
+
* @property {string} domain - Primary domain category
|
|
78
|
+
* @property {string[]} [tags=[]] - Searchable tags
|
|
79
|
+
* @property {string} [license='MIT'] - License identifier
|
|
80
|
+
* @property {boolean} [public=true] - Public visibility
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @typedef {Object} Review
|
|
85
|
+
* @property {string} id - Review ID
|
|
86
|
+
* @property {string} adapterId - Adapter ID
|
|
87
|
+
* @property {string} authorId - Review author ID
|
|
88
|
+
* @property {string} authorName - Review author name
|
|
89
|
+
* @property {number} rating - Rating (1-5)
|
|
90
|
+
* @property {string} comment - Review comment
|
|
91
|
+
* @property {number} createdAt - Creation timestamp
|
|
92
|
+
* @property {number} [helpful=0] - Helpful votes
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
// ============================================
|
|
96
|
+
// CONSTANTS
|
|
97
|
+
// ============================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Available domain categories for adapters
|
|
101
|
+
*/
|
|
102
|
+
export const ADAPTER_DOMAINS = {
|
|
103
|
+
code: {
|
|
104
|
+
name: 'Code & Programming',
|
|
105
|
+
description: 'Code generation, completion, and programming assistance',
|
|
106
|
+
icon: 'code',
|
|
107
|
+
subdomains: ['python', 'javascript', 'rust', 'go', 'sql', 'general'],
|
|
108
|
+
},
|
|
109
|
+
writing: {
|
|
110
|
+
name: 'Creative Writing',
|
|
111
|
+
description: 'Story writing, poetry, and creative content',
|
|
112
|
+
icon: 'pen',
|
|
113
|
+
subdomains: ['fiction', 'poetry', 'technical', 'copywriting', 'academic'],
|
|
114
|
+
},
|
|
115
|
+
math: {
|
|
116
|
+
name: 'Mathematics',
|
|
117
|
+
description: 'Mathematical reasoning and problem solving',
|
|
118
|
+
icon: 'calculator',
|
|
119
|
+
subdomains: ['algebra', 'calculus', 'statistics', 'geometry', 'logic'],
|
|
120
|
+
},
|
|
121
|
+
science: {
|
|
122
|
+
name: 'Science',
|
|
123
|
+
description: 'Scientific knowledge and reasoning',
|
|
124
|
+
icon: 'flask',
|
|
125
|
+
subdomains: ['physics', 'chemistry', 'biology', 'medicine', 'engineering'],
|
|
126
|
+
},
|
|
127
|
+
language: {
|
|
128
|
+
name: 'Language',
|
|
129
|
+
description: 'Language learning and translation',
|
|
130
|
+
icon: 'globe',
|
|
131
|
+
subdomains: ['translation', 'grammar', 'vocabulary', 'conversation'],
|
|
132
|
+
},
|
|
133
|
+
business: {
|
|
134
|
+
name: 'Business',
|
|
135
|
+
description: 'Business writing and analysis',
|
|
136
|
+
icon: 'briefcase',
|
|
137
|
+
subdomains: ['email', 'reports', 'analysis', 'marketing', 'legal'],
|
|
138
|
+
},
|
|
139
|
+
assistant: {
|
|
140
|
+
name: 'General Assistant',
|
|
141
|
+
description: 'General-purpose assistants and chatbots',
|
|
142
|
+
icon: 'robot',
|
|
143
|
+
subdomains: ['helpful', 'concise', 'detailed', 'friendly', 'formal'],
|
|
144
|
+
},
|
|
145
|
+
roleplay: {
|
|
146
|
+
name: 'Roleplay',
|
|
147
|
+
description: 'Character and roleplay adaptations',
|
|
148
|
+
icon: 'theater',
|
|
149
|
+
subdomains: ['characters', 'games', 'educational', 'simulation'],
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Default hub configuration
|
|
155
|
+
*/
|
|
156
|
+
const DEFAULT_HUB_CONFIG = {
|
|
157
|
+
apiEndpoint: 'https://hub.ruvector.dev/api',
|
|
158
|
+
storageEndpoint: 'https://storage.ruvector.dev',
|
|
159
|
+
cacheDir: '.ruvector/adapter-cache',
|
|
160
|
+
maxCacheSize: 500 * 1024 * 1024, // 500MB
|
|
161
|
+
enableOffline: true,
|
|
162
|
+
autoUpdate: true,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// ============================================
|
|
166
|
+
// ADAPTERHUB CLASS
|
|
167
|
+
// ============================================
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* AdapterHub - Central registry for community adapters
|
|
171
|
+
*
|
|
172
|
+
* Provides a complete ecosystem for discovering, sharing, and managing
|
|
173
|
+
* LoRA adapters. Supports offline caching, ratings/reviews, and version
|
|
174
|
+
* management.
|
|
175
|
+
*
|
|
176
|
+
* @extends EventEmitter
|
|
177
|
+
*/
|
|
178
|
+
export class AdapterHub extends EventEmitter {
|
|
179
|
+
/**
|
|
180
|
+
* Create an AdapterHub instance
|
|
181
|
+
*
|
|
182
|
+
* @param {Object} [config={}] - Hub configuration
|
|
183
|
+
*/
|
|
184
|
+
constructor(config = {}) {
|
|
185
|
+
super();
|
|
186
|
+
|
|
187
|
+
this.config = { ...DEFAULT_HUB_CONFIG, ...config };
|
|
188
|
+
this.userId = config.userId || `anon-${randomBytes(8).toString('hex')}`;
|
|
189
|
+
|
|
190
|
+
// Local cache of adapter metadata
|
|
191
|
+
this.cache = new Map();
|
|
192
|
+
|
|
193
|
+
// Downloaded adapters
|
|
194
|
+
this.downloaded = new Map();
|
|
195
|
+
|
|
196
|
+
// User's own adapters
|
|
197
|
+
this.myAdapters = new Map();
|
|
198
|
+
|
|
199
|
+
// Reviews cache
|
|
200
|
+
this.reviews = new Map();
|
|
201
|
+
|
|
202
|
+
// Stats
|
|
203
|
+
this.stats = {
|
|
204
|
+
totalBrowses: 0,
|
|
205
|
+
totalDownloads: 0,
|
|
206
|
+
totalUploads: 0,
|
|
207
|
+
cacheHits: 0,
|
|
208
|
+
cacheMisses: 0,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Initialize local storage for offline mode
|
|
212
|
+
this._initLocalStorage();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Initialize local storage for offline caching
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
219
|
+
async _initLocalStorage() {
|
|
220
|
+
try {
|
|
221
|
+
if (typeof localStorage !== 'undefined') {
|
|
222
|
+
// Browser environment
|
|
223
|
+
const cached = localStorage.getItem('ruvector-adapter-hub-cache');
|
|
224
|
+
if (cached) {
|
|
225
|
+
const data = JSON.parse(cached);
|
|
226
|
+
for (const [id, info] of Object.entries(data.adapters || {})) {
|
|
227
|
+
this.cache.set(id, info);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} else if (typeof process !== 'undefined') {
|
|
231
|
+
// Node.js environment
|
|
232
|
+
const fs = await import('fs/promises');
|
|
233
|
+
const path = await import('path');
|
|
234
|
+
const cacheFile = path.join(this.config.cacheDir, 'hub-cache.json');
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const content = await fs.readFile(cacheFile, 'utf-8');
|
|
238
|
+
const data = JSON.parse(content);
|
|
239
|
+
for (const [id, info] of Object.entries(data.adapters || {})) {
|
|
240
|
+
this.cache.set(id, info);
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
// Cache file doesn't exist yet
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('[AdapterHub] Failed to initialize local storage:', error.message);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Save cache to local storage
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
async _saveLocalStorage() {
|
|
256
|
+
try {
|
|
257
|
+
const data = {
|
|
258
|
+
adapters: Object.fromEntries(this.cache),
|
|
259
|
+
timestamp: Date.now(),
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
if (typeof localStorage !== 'undefined') {
|
|
263
|
+
localStorage.setItem('ruvector-adapter-hub-cache', JSON.stringify(data));
|
|
264
|
+
} else if (typeof process !== 'undefined') {
|
|
265
|
+
const fs = await import('fs/promises');
|
|
266
|
+
const path = await import('path');
|
|
267
|
+
await fs.mkdir(this.config.cacheDir, { recursive: true });
|
|
268
|
+
const cacheFile = path.join(this.config.cacheDir, 'hub-cache.json');
|
|
269
|
+
await fs.writeFile(cacheFile, JSON.stringify(data, null, 2));
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error('[AdapterHub] Failed to save local storage:', error.message);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================
|
|
277
|
+
// BROWSING AND DISCOVERY
|
|
278
|
+
// ============================================
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Browse available adapters with filtering and sorting
|
|
282
|
+
*
|
|
283
|
+
* @param {BrowseOptions} [options={}] - Browse options
|
|
284
|
+
* @returns {Promise<{adapters: AdapterInfo[], total: number, hasMore: boolean}>}
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```javascript
|
|
288
|
+
* // Browse code adapters sorted by rating
|
|
289
|
+
* const results = await hub.browse({
|
|
290
|
+
* domain: 'code',
|
|
291
|
+
* sort: 'rating',
|
|
292
|
+
* limit: 10
|
|
293
|
+
* });
|
|
294
|
+
*
|
|
295
|
+
* for (const adapter of results.adapters) {
|
|
296
|
+
* console.log(`${adapter.name}: ${adapter.rating} stars`);
|
|
297
|
+
* }
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
async browse(options = {}) {
|
|
301
|
+
const opts = {
|
|
302
|
+
domain: null,
|
|
303
|
+
baseModel: null,
|
|
304
|
+
query: null,
|
|
305
|
+
tags: [],
|
|
306
|
+
sort: 'downloads',
|
|
307
|
+
limit: 20,
|
|
308
|
+
offset: 0,
|
|
309
|
+
minRating: 0,
|
|
310
|
+
...options,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
this.stats.totalBrowses++;
|
|
314
|
+
this.emit('browse:start', opts);
|
|
315
|
+
|
|
316
|
+
// Filter adapters from cache
|
|
317
|
+
let adapters = Array.from(this.cache.values());
|
|
318
|
+
|
|
319
|
+
// Apply filters
|
|
320
|
+
if (opts.domain) {
|
|
321
|
+
adapters = adapters.filter(a => a.domain === opts.domain);
|
|
322
|
+
}
|
|
323
|
+
if (opts.baseModel) {
|
|
324
|
+
adapters = adapters.filter(a => a.baseModel === opts.baseModel);
|
|
325
|
+
}
|
|
326
|
+
if (opts.minRating > 0) {
|
|
327
|
+
adapters = adapters.filter(a => a.rating >= opts.minRating);
|
|
328
|
+
}
|
|
329
|
+
if (opts.tags && opts.tags.length > 0) {
|
|
330
|
+
adapters = adapters.filter(a =>
|
|
331
|
+
opts.tags.some(tag => a.tags?.includes(tag))
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
if (opts.query) {
|
|
335
|
+
const query = opts.query.toLowerCase();
|
|
336
|
+
adapters = adapters.filter(a =>
|
|
337
|
+
a.name?.toLowerCase().includes(query) ||
|
|
338
|
+
a.description?.toLowerCase().includes(query) ||
|
|
339
|
+
a.tags?.some(t => t.toLowerCase().includes(query))
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Sort
|
|
344
|
+
switch (opts.sort) {
|
|
345
|
+
case 'rating':
|
|
346
|
+
adapters.sort((a, b) => (b.rating || 0) - (a.rating || 0));
|
|
347
|
+
break;
|
|
348
|
+
case 'downloads':
|
|
349
|
+
adapters.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
|
|
350
|
+
break;
|
|
351
|
+
case 'recent':
|
|
352
|
+
adapters.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
353
|
+
break;
|
|
354
|
+
case 'updated':
|
|
355
|
+
adapters.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
356
|
+
break;
|
|
357
|
+
case 'name':
|
|
358
|
+
adapters.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Paginate
|
|
363
|
+
const total = adapters.length;
|
|
364
|
+
const paged = adapters.slice(opts.offset, opts.offset + opts.limit);
|
|
365
|
+
|
|
366
|
+
const result = {
|
|
367
|
+
adapters: paged,
|
|
368
|
+
total,
|
|
369
|
+
hasMore: opts.offset + opts.limit < total,
|
|
370
|
+
offset: opts.offset,
|
|
371
|
+
limit: opts.limit,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
this.emit('browse:complete', result);
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Search adapters by query string
|
|
380
|
+
*
|
|
381
|
+
* @param {string} query - Search query
|
|
382
|
+
* @param {Object} [options={}] - Additional filters
|
|
383
|
+
* @returns {Promise<AdapterInfo[]>}
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```javascript
|
|
387
|
+
* const results = await hub.search('python code completion');
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
async search(query, options = {}) {
|
|
391
|
+
return this.browse({ ...options, query });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get featured/recommended adapters
|
|
396
|
+
*
|
|
397
|
+
* @param {number} [limit=10] - Number of adapters to return
|
|
398
|
+
* @returns {Promise<AdapterInfo[]>}
|
|
399
|
+
*/
|
|
400
|
+
async getFeatured(limit = 10) {
|
|
401
|
+
const result = await this.browse({
|
|
402
|
+
sort: 'rating',
|
|
403
|
+
minRating: 4.0,
|
|
404
|
+
limit,
|
|
405
|
+
});
|
|
406
|
+
return result.adapters;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get trending adapters (most downloaded recently)
|
|
411
|
+
*
|
|
412
|
+
* @param {number} [limit=10] - Number of adapters to return
|
|
413
|
+
* @returns {Promise<AdapterInfo[]>}
|
|
414
|
+
*/
|
|
415
|
+
async getTrending(limit = 10) {
|
|
416
|
+
const result = await this.browse({
|
|
417
|
+
sort: 'downloads',
|
|
418
|
+
limit,
|
|
419
|
+
});
|
|
420
|
+
return result.adapters;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get adapters by domain category
|
|
425
|
+
*
|
|
426
|
+
* @param {string} domain - Domain category
|
|
427
|
+
* @param {Object} [options={}] - Additional options
|
|
428
|
+
* @returns {Promise<AdapterInfo[]>}
|
|
429
|
+
*/
|
|
430
|
+
async getByDomain(domain, options = {}) {
|
|
431
|
+
const result = await this.browse({ ...options, domain });
|
|
432
|
+
return result.adapters;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get available domain categories with counts
|
|
437
|
+
*
|
|
438
|
+
* @returns {Promise<Array<{domain: string, count: number, info: Object}>>}
|
|
439
|
+
*/
|
|
440
|
+
async getDomains() {
|
|
441
|
+
const counts = {};
|
|
442
|
+
for (const adapter of this.cache.values()) {
|
|
443
|
+
counts[adapter.domain] = (counts[adapter.domain] || 0) + 1;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return Object.entries(ADAPTER_DOMAINS).map(([id, info]) => ({
|
|
447
|
+
domain: id,
|
|
448
|
+
count: counts[id] || 0,
|
|
449
|
+
...info,
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ============================================
|
|
454
|
+
// DOWNLOAD AND APPLY
|
|
455
|
+
// ============================================
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Download an adapter by ID
|
|
459
|
+
*
|
|
460
|
+
* @param {string} adapterId - Adapter identifier
|
|
461
|
+
* @param {Object} [options={}] - Download options
|
|
462
|
+
* @returns {Promise<Object>} Adapter data
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```javascript
|
|
466
|
+
* const adapter = await hub.download('code-assistant-v2');
|
|
467
|
+
* await myLoRA.loadAdapter(adapter);
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
async download(adapterId, options = {}) {
|
|
471
|
+
this.stats.totalDownloads++;
|
|
472
|
+
this.emit('download:start', { adapterId });
|
|
473
|
+
|
|
474
|
+
// Check local cache first
|
|
475
|
+
if (this.downloaded.has(adapterId)) {
|
|
476
|
+
this.stats.cacheHits++;
|
|
477
|
+
const cached = this.downloaded.get(adapterId);
|
|
478
|
+
this.emit('download:complete', { adapterId, cached: true });
|
|
479
|
+
return cached;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
this.stats.cacheMisses++;
|
|
483
|
+
|
|
484
|
+
// Get adapter info
|
|
485
|
+
const info = this.cache.get(adapterId);
|
|
486
|
+
if (!info) {
|
|
487
|
+
throw new Error(`Adapter not found: ${adapterId}`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Simulate download (in production, would fetch from storage)
|
|
491
|
+
const adapterData = this._generateMockAdapter(info);
|
|
492
|
+
|
|
493
|
+
// Cache downloaded adapter
|
|
494
|
+
this.downloaded.set(adapterId, adapterData);
|
|
495
|
+
|
|
496
|
+
// Update download count
|
|
497
|
+
info.downloads = (info.downloads || 0) + 1;
|
|
498
|
+
this.cache.set(adapterId, info);
|
|
499
|
+
await this._saveLocalStorage();
|
|
500
|
+
|
|
501
|
+
this.emit('download:complete', { adapterId, cached: false, size: JSON.stringify(adapterData).length });
|
|
502
|
+
return adapterData;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Generate mock adapter data for demo purposes
|
|
507
|
+
* @private
|
|
508
|
+
*/
|
|
509
|
+
_generateMockAdapter(info) {
|
|
510
|
+
const rank = info.config?.rank || 4;
|
|
511
|
+
const dim = info.config?.embeddingDim || 384;
|
|
512
|
+
|
|
513
|
+
const adapters = {};
|
|
514
|
+
for (const module of ['query', 'value', 'key', 'dense']) {
|
|
515
|
+
adapters[module] = {
|
|
516
|
+
loraA: this._generateRandomMatrix(dim, rank),
|
|
517
|
+
loraB: this._generateRandomMatrix(rank, dim),
|
|
518
|
+
scaling: (info.config?.alpha || 8) / rank,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
version: '1.0.0',
|
|
524
|
+
format: 'microlora',
|
|
525
|
+
metadata: {
|
|
526
|
+
id: info.id,
|
|
527
|
+
name: info.name,
|
|
528
|
+
description: info.description,
|
|
529
|
+
baseModel: info.baseModel,
|
|
530
|
+
domain: info.domain,
|
|
531
|
+
rank: rank,
|
|
532
|
+
alpha: info.config?.alpha || 8,
|
|
533
|
+
trainingSamples: info.stats?.trainingSamples || 0,
|
|
534
|
+
trainingEpochs: info.stats?.trainingEpochs || 0,
|
|
535
|
+
createdAt: info.createdAt,
|
|
536
|
+
version: info.version,
|
|
537
|
+
},
|
|
538
|
+
config: info.config,
|
|
539
|
+
baseModel: info.baseModel,
|
|
540
|
+
adapters,
|
|
541
|
+
stats: info.stats,
|
|
542
|
+
createdAt: info.createdAt,
|
|
543
|
+
savedAt: Date.now(),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Generate random matrix for mock data
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
_generateRandomMatrix(rows, cols) {
|
|
552
|
+
const matrix = [];
|
|
553
|
+
const std = Math.sqrt(2 / (rows + cols)) * 0.1;
|
|
554
|
+
for (let i = 0; i < rows; i++) {
|
|
555
|
+
const row = [];
|
|
556
|
+
for (let j = 0; j < cols; j++) {
|
|
557
|
+
row.push((Math.random() - 0.5) * 2 * std);
|
|
558
|
+
}
|
|
559
|
+
matrix.push(row);
|
|
560
|
+
}
|
|
561
|
+
return matrix;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Check if an adapter is downloaded locally
|
|
566
|
+
*
|
|
567
|
+
* @param {string} adapterId - Adapter identifier
|
|
568
|
+
* @returns {boolean}
|
|
569
|
+
*/
|
|
570
|
+
isDownloaded(adapterId) {
|
|
571
|
+
return this.downloaded.has(adapterId);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Remove a downloaded adapter from local cache
|
|
576
|
+
*
|
|
577
|
+
* @param {string} adapterId - Adapter identifier
|
|
578
|
+
*/
|
|
579
|
+
removeDownloaded(adapterId) {
|
|
580
|
+
this.downloaded.delete(adapterId);
|
|
581
|
+
this.emit('adapter:removed', { adapterId });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ============================================
|
|
585
|
+
// UPLOAD AND SHARE
|
|
586
|
+
// ============================================
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Upload an adapter to the hub
|
|
590
|
+
*
|
|
591
|
+
* @param {Object} adapter - MicroLoRA instance or adapter data
|
|
592
|
+
* @param {UploadOptions} options - Upload options
|
|
593
|
+
* @returns {Promise<AdapterInfo>} Uploaded adapter info
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* ```javascript
|
|
597
|
+
* const info = await hub.upload(myLoRA, {
|
|
598
|
+
* name: 'Python Code Assistant',
|
|
599
|
+
* description: 'Specialized for Python coding tasks',
|
|
600
|
+
* domain: 'code',
|
|
601
|
+
* tags: ['python', 'coding', 'assistant']
|
|
602
|
+
* });
|
|
603
|
+
* console.log(`Uploaded: ${info.id}`);
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
async upload(adapter, options) {
|
|
607
|
+
const opts = {
|
|
608
|
+
name: 'Untitled Adapter',
|
|
609
|
+
description: '',
|
|
610
|
+
domain: 'general',
|
|
611
|
+
tags: [],
|
|
612
|
+
license: 'MIT',
|
|
613
|
+
public: true,
|
|
614
|
+
...options,
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
this.stats.totalUploads++;
|
|
618
|
+
this.emit('upload:start', { name: opts.name });
|
|
619
|
+
|
|
620
|
+
// Get adapter data
|
|
621
|
+
let adapterData;
|
|
622
|
+
if (typeof adapter.saveAdapter === 'function') {
|
|
623
|
+
adapterData = await adapter.saveAdapter();
|
|
624
|
+
} else {
|
|
625
|
+
adapterData = adapter;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Generate unique ID
|
|
629
|
+
const id = `${opts.domain}-${opts.name.toLowerCase().replace(/\s+/g, '-')}-${randomBytes(4).toString('hex')}`;
|
|
630
|
+
|
|
631
|
+
// Create adapter info
|
|
632
|
+
const info = {
|
|
633
|
+
id,
|
|
634
|
+
name: opts.name,
|
|
635
|
+
description: opts.description,
|
|
636
|
+
author: this.userId,
|
|
637
|
+
authorId: this.userId,
|
|
638
|
+
baseModel: adapterData.baseModel || 'unknown',
|
|
639
|
+
domain: opts.domain,
|
|
640
|
+
tags: opts.tags,
|
|
641
|
+
rating: 0,
|
|
642
|
+
ratingCount: 0,
|
|
643
|
+
downloads: 0,
|
|
644
|
+
size: JSON.stringify(adapterData).length,
|
|
645
|
+
version: adapterData.version || '1.0.0',
|
|
646
|
+
license: opts.license,
|
|
647
|
+
createdAt: Date.now(),
|
|
648
|
+
updatedAt: Date.now(),
|
|
649
|
+
config: adapterData.config,
|
|
650
|
+
stats: adapterData.stats,
|
|
651
|
+
public: opts.public,
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// Store in cache and my adapters
|
|
655
|
+
this.cache.set(id, info);
|
|
656
|
+
this.myAdapters.set(id, { info, data: adapterData });
|
|
657
|
+
await this._saveLocalStorage();
|
|
658
|
+
|
|
659
|
+
this.emit('upload:complete', info);
|
|
660
|
+
return info;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Update an uploaded adapter
|
|
665
|
+
*
|
|
666
|
+
* @param {string} adapterId - Adapter to update
|
|
667
|
+
* @param {Object} adapter - New adapter data
|
|
668
|
+
* @param {Object} [options={}] - Update options
|
|
669
|
+
* @returns {Promise<AdapterInfo>}
|
|
670
|
+
*/
|
|
671
|
+
async update(adapterId, adapter, options = {}) {
|
|
672
|
+
const existing = this.myAdapters.get(adapterId);
|
|
673
|
+
if (!existing) {
|
|
674
|
+
throw new Error(`Adapter not found in your uploads: ${adapterId}`);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
let adapterData;
|
|
678
|
+
if (typeof adapter.saveAdapter === 'function') {
|
|
679
|
+
adapterData = await adapter.saveAdapter();
|
|
680
|
+
} else {
|
|
681
|
+
adapterData = adapter;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Update info
|
|
685
|
+
const info = {
|
|
686
|
+
...existing.info,
|
|
687
|
+
...options,
|
|
688
|
+
updatedAt: Date.now(),
|
|
689
|
+
size: JSON.stringify(adapterData).length,
|
|
690
|
+
config: adapterData.config,
|
|
691
|
+
stats: adapterData.stats,
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// Increment version
|
|
695
|
+
const versionParts = (info.version || '1.0.0').split('.').map(Number);
|
|
696
|
+
versionParts[2]++;
|
|
697
|
+
info.version = versionParts.join('.');
|
|
698
|
+
|
|
699
|
+
// Update cache
|
|
700
|
+
this.cache.set(adapterId, info);
|
|
701
|
+
this.myAdapters.set(adapterId, { info, data: adapterData });
|
|
702
|
+
await this._saveLocalStorage();
|
|
703
|
+
|
|
704
|
+
this.emit('update:complete', info);
|
|
705
|
+
return info;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Delete an uploaded adapter
|
|
710
|
+
*
|
|
711
|
+
* @param {string} adapterId - Adapter to delete
|
|
712
|
+
* @returns {Promise<boolean>}
|
|
713
|
+
*/
|
|
714
|
+
async delete(adapterId) {
|
|
715
|
+
if (!this.myAdapters.has(adapterId)) {
|
|
716
|
+
throw new Error(`Adapter not found in your uploads: ${adapterId}`);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
this.cache.delete(adapterId);
|
|
720
|
+
this.myAdapters.delete(adapterId);
|
|
721
|
+
this.downloaded.delete(adapterId);
|
|
722
|
+
this.reviews.delete(adapterId);
|
|
723
|
+
|
|
724
|
+
await this._saveLocalStorage();
|
|
725
|
+
this.emit('delete:complete', { adapterId });
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Get user's uploaded adapters
|
|
731
|
+
*
|
|
732
|
+
* @returns {AdapterInfo[]}
|
|
733
|
+
*/
|
|
734
|
+
getMyAdapters() {
|
|
735
|
+
return Array.from(this.myAdapters.values()).map(a => a.info);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// ============================================
|
|
739
|
+
// RATINGS AND REVIEWS
|
|
740
|
+
// ============================================
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Rate an adapter
|
|
744
|
+
*
|
|
745
|
+
* @param {string} adapterId - Adapter to rate
|
|
746
|
+
* @param {number} rating - Rating (1-5)
|
|
747
|
+
* @returns {Promise<void>}
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```javascript
|
|
751
|
+
* await hub.rate('code-assistant-v2', 5);
|
|
752
|
+
* ```
|
|
753
|
+
*/
|
|
754
|
+
async rate(adapterId, rating) {
|
|
755
|
+
if (rating < 1 || rating > 5) {
|
|
756
|
+
throw new Error('Rating must be between 1 and 5');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const info = this.cache.get(adapterId);
|
|
760
|
+
if (!info) {
|
|
761
|
+
throw new Error(`Adapter not found: ${adapterId}`);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Update rating (running average)
|
|
765
|
+
const totalRating = (info.rating || 0) * (info.ratingCount || 0);
|
|
766
|
+
info.ratingCount = (info.ratingCount || 0) + 1;
|
|
767
|
+
info.rating = (totalRating + rating) / info.ratingCount;
|
|
768
|
+
|
|
769
|
+
this.cache.set(adapterId, info);
|
|
770
|
+
await this._saveLocalStorage();
|
|
771
|
+
|
|
772
|
+
this.emit('rating:added', { adapterId, rating, newRating: info.rating });
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Add a review for an adapter
|
|
777
|
+
*
|
|
778
|
+
* @param {string} adapterId - Adapter to review
|
|
779
|
+
* @param {number} rating - Rating (1-5)
|
|
780
|
+
* @param {string} comment - Review comment
|
|
781
|
+
* @returns {Promise<Review>}
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* ```javascript
|
|
785
|
+
* const review = await hub.review('code-assistant-v2', 5, 'Great for Python!');
|
|
786
|
+
* ```
|
|
787
|
+
*/
|
|
788
|
+
async review(adapterId, rating, comment) {
|
|
789
|
+
// Add rating first
|
|
790
|
+
await this.rate(adapterId, rating);
|
|
791
|
+
|
|
792
|
+
// Create review
|
|
793
|
+
const reviewData = {
|
|
794
|
+
id: `review-${randomBytes(6).toString('hex')}`,
|
|
795
|
+
adapterId,
|
|
796
|
+
authorId: this.userId,
|
|
797
|
+
authorName: `User-${this.userId.slice(0, 8)}`,
|
|
798
|
+
rating,
|
|
799
|
+
comment,
|
|
800
|
+
createdAt: Date.now(),
|
|
801
|
+
helpful: 0,
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// Store review
|
|
805
|
+
if (!this.reviews.has(adapterId)) {
|
|
806
|
+
this.reviews.set(adapterId, []);
|
|
807
|
+
}
|
|
808
|
+
this.reviews.get(adapterId).push(reviewData);
|
|
809
|
+
|
|
810
|
+
this.emit('review:added', reviewData);
|
|
811
|
+
return reviewData;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Get reviews for an adapter
|
|
816
|
+
*
|
|
817
|
+
* @param {string} adapterId - Adapter ID
|
|
818
|
+
* @param {Object} [options={}] - Options
|
|
819
|
+
* @returns {Promise<Review[]>}
|
|
820
|
+
*/
|
|
821
|
+
async getReviews(adapterId, options = {}) {
|
|
822
|
+
const { sort = 'recent', limit = 20 } = options;
|
|
823
|
+
|
|
824
|
+
const adapterReviews = this.reviews.get(adapterId) || [];
|
|
825
|
+
|
|
826
|
+
// Sort
|
|
827
|
+
const sorted = [...adapterReviews];
|
|
828
|
+
switch (sort) {
|
|
829
|
+
case 'helpful':
|
|
830
|
+
sorted.sort((a, b) => (b.helpful || 0) - (a.helpful || 0));
|
|
831
|
+
break;
|
|
832
|
+
case 'rating':
|
|
833
|
+
sorted.sort((a, b) => b.rating - a.rating);
|
|
834
|
+
break;
|
|
835
|
+
case 'recent':
|
|
836
|
+
default:
|
|
837
|
+
sorted.sort((a, b) => b.createdAt - a.createdAt);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return sorted.slice(0, limit);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Mark a review as helpful
|
|
845
|
+
*
|
|
846
|
+
* @param {string} reviewId - Review ID
|
|
847
|
+
*/
|
|
848
|
+
async markHelpful(reviewId) {
|
|
849
|
+
for (const reviews of this.reviews.values()) {
|
|
850
|
+
const review = reviews.find(r => r.id === reviewId);
|
|
851
|
+
if (review) {
|
|
852
|
+
review.helpful = (review.helpful || 0) + 1;
|
|
853
|
+
this.emit('review:helpful', { reviewId, helpful: review.helpful });
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// ============================================
|
|
860
|
+
// COLLECTIONS AND FAVORITES
|
|
861
|
+
// ============================================
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Create a collection of adapters
|
|
865
|
+
*
|
|
866
|
+
* @param {string} name - Collection name
|
|
867
|
+
* @param {string} [description=''] - Collection description
|
|
868
|
+
* @returns {Object} Collection info
|
|
869
|
+
*/
|
|
870
|
+
createCollection(name, description = '') {
|
|
871
|
+
const id = `collection-${randomBytes(6).toString('hex')}`;
|
|
872
|
+
const collection = {
|
|
873
|
+
id,
|
|
874
|
+
name,
|
|
875
|
+
description,
|
|
876
|
+
adapters: [],
|
|
877
|
+
createdAt: Date.now(),
|
|
878
|
+
updatedAt: Date.now(),
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
this.emit('collection:created', collection);
|
|
882
|
+
return collection;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Add adapter to favorites
|
|
887
|
+
*
|
|
888
|
+
* @param {string} adapterId - Adapter to favorite
|
|
889
|
+
*/
|
|
890
|
+
addFavorite(adapterId) {
|
|
891
|
+
this.emit('favorite:added', { adapterId });
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Remove adapter from favorites
|
|
896
|
+
*
|
|
897
|
+
* @param {string} adapterId - Adapter to unfavorite
|
|
898
|
+
*/
|
|
899
|
+
removeFavorite(adapterId) {
|
|
900
|
+
this.emit('favorite:removed', { adapterId });
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// ============================================
|
|
904
|
+
// UTILITY METHODS
|
|
905
|
+
// ============================================
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Get detailed info about an adapter
|
|
909
|
+
*
|
|
910
|
+
* @param {string} adapterId - Adapter ID
|
|
911
|
+
* @returns {Promise<AdapterInfo|null>}
|
|
912
|
+
*/
|
|
913
|
+
async getAdapterInfo(adapterId) {
|
|
914
|
+
return this.cache.get(adapterId) || null;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Check if an adapter exists
|
|
919
|
+
*
|
|
920
|
+
* @param {string} adapterId - Adapter ID
|
|
921
|
+
* @returns {boolean}
|
|
922
|
+
*/
|
|
923
|
+
exists(adapterId) {
|
|
924
|
+
return this.cache.has(adapterId);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Get hub statistics
|
|
929
|
+
*
|
|
930
|
+
* @returns {Object}
|
|
931
|
+
*/
|
|
932
|
+
getStats() {
|
|
933
|
+
return {
|
|
934
|
+
...this.stats,
|
|
935
|
+
totalAdapters: this.cache.size,
|
|
936
|
+
downloadedAdapters: this.downloaded.size,
|
|
937
|
+
myAdapters: this.myAdapters.size,
|
|
938
|
+
totalReviews: Array.from(this.reviews.values()).reduce((sum, r) => sum + r.length, 0),
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Clear all caches
|
|
944
|
+
*/
|
|
945
|
+
async clearCache() {
|
|
946
|
+
this.downloaded.clear();
|
|
947
|
+
this.emit('cache:cleared');
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Seed hub with sample adapters for demo purposes
|
|
952
|
+
*
|
|
953
|
+
* @param {number} [count=20] - Number of sample adapters
|
|
954
|
+
*/
|
|
955
|
+
async seedSampleAdapters(count = 20) {
|
|
956
|
+
const domains = Object.keys(ADAPTER_DOMAINS);
|
|
957
|
+
const models = ['phi-1.5-int4', 'distilgpt2', 'gpt2', 'starcoder-tiny'];
|
|
958
|
+
const adjectives = ['Advanced', 'Pro', 'Ultra', 'Smart', 'Fast', 'Accurate', 'Helpful'];
|
|
959
|
+
const nouns = ['Assistant', 'Helper', 'Expert', 'Companion', 'Generator', 'Wizard'];
|
|
960
|
+
|
|
961
|
+
for (let i = 0; i < count; i++) {
|
|
962
|
+
const domain = domains[Math.floor(Math.random() * domains.length)];
|
|
963
|
+
const model = models[Math.floor(Math.random() * models.length)];
|
|
964
|
+
const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
|
|
965
|
+
const noun = nouns[Math.floor(Math.random() * nouns.length)];
|
|
966
|
+
const name = `${adj} ${ADAPTER_DOMAINS[domain].name.split(' ')[0]} ${noun}`;
|
|
967
|
+
|
|
968
|
+
const info = {
|
|
969
|
+
id: `sample-${domain}-${randomBytes(4).toString('hex')}`,
|
|
970
|
+
name,
|
|
971
|
+
description: `A ${adj.toLowerCase()} adapter for ${ADAPTER_DOMAINS[domain].description.toLowerCase()}`,
|
|
972
|
+
author: `sample-author-${i % 5}`,
|
|
973
|
+
authorId: `sample-author-${i % 5}`,
|
|
974
|
+
baseModel: model,
|
|
975
|
+
domain,
|
|
976
|
+
tags: [domain, ...ADAPTER_DOMAINS[domain].subdomains.slice(0, 2)],
|
|
977
|
+
rating: 3 + Math.random() * 2,
|
|
978
|
+
ratingCount: Math.floor(Math.random() * 100) + 1,
|
|
979
|
+
downloads: Math.floor(Math.random() * 10000),
|
|
980
|
+
size: Math.floor(Math.random() * 50000) + 10000,
|
|
981
|
+
version: `1.${Math.floor(Math.random() * 10)}.0`,
|
|
982
|
+
license: 'MIT',
|
|
983
|
+
createdAt: Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000),
|
|
984
|
+
updatedAt: Date.now() - Math.floor(Math.random() * 7 * 24 * 60 * 60 * 1000),
|
|
985
|
+
config: {
|
|
986
|
+
rank: [4, 8, 16][Math.floor(Math.random() * 3)],
|
|
987
|
+
alpha: [8, 16, 32][Math.floor(Math.random() * 3)],
|
|
988
|
+
embeddingDim: 384,
|
|
989
|
+
},
|
|
990
|
+
stats: {
|
|
991
|
+
trainingSamples: Math.floor(Math.random() * 10000) + 100,
|
|
992
|
+
trainingEpochs: Math.floor(Math.random() * 50) + 5,
|
|
993
|
+
},
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
this.cache.set(info.id, info);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
await this._saveLocalStorage();
|
|
1000
|
+
this.emit('seed:complete', { count });
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// ============================================
|
|
1005
|
+
// EXPORTS
|
|
1006
|
+
// ============================================
|
|
1007
|
+
|
|
1008
|
+
export default AdapterHub;
|