llm-checker 3.6.1 → 3.7.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/README.md +14 -7
- package/bin/enhanced_cli.js +361 -5
- package/package.json +7 -2
- package/src/data/model-database.js +450 -0
- package/src/data/registry-ingestors.js +751 -0
- package/src/data/registry-recommender.js +514 -0
- package/src/data/seed/README.md +11 -3
- package/src/data/seed/models.db +0 -0
- package/src/index.js +68 -4
- package/src/models/deterministic-selector.js +16 -3
- package/src/models/moe-assumptions.js +11 -0
|
@@ -13,6 +13,7 @@ class ModelDatabase {
|
|
|
13
13
|
this.seedDbPath = options.seedDbPath || path.join(__dirname, 'seed', 'models.db');
|
|
14
14
|
this.db = null;
|
|
15
15
|
this.initialized = false;
|
|
16
|
+
this.disableRegistrySeedImport = Boolean(options.disableRegistrySeedImport);
|
|
16
17
|
// Batched-write state: during a bulk sync we defer the (expensive) full
|
|
17
18
|
// sql.js export-and-write until the batch ends, instead of rewriting the
|
|
18
19
|
// whole DB file on every single row.
|
|
@@ -65,6 +66,9 @@ class ModelDatabase {
|
|
|
65
66
|
|
|
66
67
|
this.createSchema();
|
|
67
68
|
this.initialized = true;
|
|
69
|
+
if (!this.disableRegistrySeedImport) {
|
|
70
|
+
await this.seedRegistryFromPackagedSnapshotIfNeeded();
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
/**
|
|
@@ -119,6 +123,86 @@ class ModelDatabase {
|
|
|
119
123
|
FOREIGN KEY (variant_id) REFERENCES variants(id) ON DELETE CASCADE
|
|
120
124
|
);
|
|
121
125
|
|
|
126
|
+
-- Registry sources for multi-hub model discovery (Hugging Face,
|
|
127
|
+
-- Ollama, GPT4All, ModelScope, etc.).
|
|
128
|
+
CREATE TABLE IF NOT EXISTS registry_sources (
|
|
129
|
+
id TEXT PRIMARY KEY,
|
|
130
|
+
name TEXT NOT NULL,
|
|
131
|
+
base_url TEXT,
|
|
132
|
+
source_type TEXT,
|
|
133
|
+
last_ingested_at TEXT,
|
|
134
|
+
metadata TEXT DEFAULT '{}',
|
|
135
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
136
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
-- Source repositories or registry entries. A single repo can expose
|
|
140
|
+
-- many downloadable artifacts/quantizations.
|
|
141
|
+
CREATE TABLE IF NOT EXISTS registry_repos (
|
|
142
|
+
id TEXT PRIMARY KEY,
|
|
143
|
+
source_id TEXT NOT NULL,
|
|
144
|
+
repo_id TEXT NOT NULL,
|
|
145
|
+
namespace TEXT,
|
|
146
|
+
canonical_model_id TEXT NOT NULL,
|
|
147
|
+
display_name TEXT,
|
|
148
|
+
url TEXT,
|
|
149
|
+
license TEXT,
|
|
150
|
+
gated INTEGER DEFAULT 0,
|
|
151
|
+
requires_auth INTEGER DEFAULT 0,
|
|
152
|
+
downloads INTEGER DEFAULT 0,
|
|
153
|
+
likes INTEGER DEFAULT 0,
|
|
154
|
+
tags TEXT DEFAULT '[]',
|
|
155
|
+
tasks TEXT DEFAULT '[]',
|
|
156
|
+
modalities TEXT DEFAULT '["text"]',
|
|
157
|
+
last_modified TEXT,
|
|
158
|
+
sha TEXT,
|
|
159
|
+
metadata TEXT DEFAULT '{}',
|
|
160
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
161
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
162
|
+
FOREIGN KEY (source_id) REFERENCES registry_sources(id) ON DELETE CASCADE,
|
|
163
|
+
UNIQUE(source_id, repo_id)
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
-- Concrete downloadable/installable files or tags used by the
|
|
167
|
+
-- recommender. This is the table that lets llm-checker reason about
|
|
168
|
+
-- exact GGUF/safetensors/MLX/Ollama variants instead of only families.
|
|
169
|
+
CREATE TABLE IF NOT EXISTS model_artifacts (
|
|
170
|
+
id TEXT PRIMARY KEY,
|
|
171
|
+
source_id TEXT NOT NULL,
|
|
172
|
+
repo_key TEXT NOT NULL,
|
|
173
|
+
repo_id TEXT NOT NULL,
|
|
174
|
+
canonical_model_id TEXT NOT NULL,
|
|
175
|
+
artifact_name TEXT NOT NULL,
|
|
176
|
+
filename TEXT,
|
|
177
|
+
format TEXT,
|
|
178
|
+
quantization TEXT,
|
|
179
|
+
precision TEXT,
|
|
180
|
+
parameter_count_b REAL,
|
|
181
|
+
active_parameter_count_b REAL,
|
|
182
|
+
size_bytes INTEGER,
|
|
183
|
+
size_gb REAL,
|
|
184
|
+
context_length INTEGER,
|
|
185
|
+
runtime_support TEXT DEFAULT '[]',
|
|
186
|
+
tasks TEXT DEFAULT '[]',
|
|
187
|
+
modalities TEXT DEFAULT '["text"]',
|
|
188
|
+
download_url TEXT,
|
|
189
|
+
install_command TEXT,
|
|
190
|
+
sha256 TEXT,
|
|
191
|
+
etag TEXT,
|
|
192
|
+
license TEXT,
|
|
193
|
+
gated INTEGER DEFAULT 0,
|
|
194
|
+
requires_auth INTEGER DEFAULT 0,
|
|
195
|
+
downloads INTEGER DEFAULT 0,
|
|
196
|
+
likes INTEGER DEFAULT 0,
|
|
197
|
+
updated_at TEXT,
|
|
198
|
+
metadata TEXT DEFAULT '{}',
|
|
199
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
200
|
+
refreshed_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
201
|
+
FOREIGN KEY (source_id) REFERENCES registry_sources(id) ON DELETE CASCADE,
|
|
202
|
+
FOREIGN KEY (repo_key) REFERENCES registry_repos(id) ON DELETE CASCADE,
|
|
203
|
+
UNIQUE(source_id, repo_key, artifact_name)
|
|
204
|
+
);
|
|
205
|
+
|
|
122
206
|
-- Sync metadata table
|
|
123
207
|
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
124
208
|
key TEXT PRIMARY KEY,
|
|
@@ -136,6 +220,16 @@ class ModelDatabase {
|
|
|
136
220
|
CREATE INDEX IF NOT EXISTS idx_variants_model ON variants(model_id);
|
|
137
221
|
CREATE INDEX IF NOT EXISTS idx_benchmarks_hardware ON benchmarks(hardware_fingerprint);
|
|
138
222
|
CREATE INDEX IF NOT EXISTS idx_benchmarks_variant ON benchmarks(variant_id);
|
|
223
|
+
CREATE INDEX IF NOT EXISTS idx_registry_repos_source ON registry_repos(source_id);
|
|
224
|
+
CREATE INDEX IF NOT EXISTS idx_registry_repos_model ON registry_repos(canonical_model_id);
|
|
225
|
+
CREATE INDEX IF NOT EXISTS idx_registry_repos_downloads ON registry_repos(downloads DESC);
|
|
226
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_model ON model_artifacts(canonical_model_id);
|
|
227
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_source ON model_artifacts(source_id);
|
|
228
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_format ON model_artifacts(format);
|
|
229
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_quant ON model_artifacts(quantization);
|
|
230
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_runtime ON model_artifacts(runtime_support);
|
|
231
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_size ON model_artifacts(size_gb);
|
|
232
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_downloads ON model_artifacts(downloads DESC);
|
|
139
233
|
`;
|
|
140
234
|
|
|
141
235
|
if (this.useBetterSqlite) {
|
|
@@ -365,6 +459,343 @@ class ModelDatabase {
|
|
|
365
459
|
]);
|
|
366
460
|
}
|
|
367
461
|
|
|
462
|
+
// ==================== MODEL REGISTRY OPERATIONS ====================
|
|
463
|
+
|
|
464
|
+
stringifyJson(value, fallback) {
|
|
465
|
+
const safeValue = value === undefined ? fallback : value;
|
|
466
|
+
try {
|
|
467
|
+
return JSON.stringify(safeValue);
|
|
468
|
+
} catch {
|
|
469
|
+
return JSON.stringify(fallback);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
parseJson(value, fallback) {
|
|
474
|
+
if (!value) return fallback;
|
|
475
|
+
try {
|
|
476
|
+
const parsed = JSON.parse(value);
|
|
477
|
+
return parsed;
|
|
478
|
+
} catch {
|
|
479
|
+
return fallback;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
upsertRegistrySource(source) {
|
|
484
|
+
const sql = `
|
|
485
|
+
INSERT INTO registry_sources (id, name, base_url, source_type, last_ingested_at, metadata, updated_at)
|
|
486
|
+
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
487
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
488
|
+
name = excluded.name,
|
|
489
|
+
base_url = excluded.base_url,
|
|
490
|
+
source_type = excluded.source_type,
|
|
491
|
+
last_ingested_at = excluded.last_ingested_at,
|
|
492
|
+
metadata = excluded.metadata,
|
|
493
|
+
updated_at = CURRENT_TIMESTAMP
|
|
494
|
+
`;
|
|
495
|
+
|
|
496
|
+
this.run(sql, [
|
|
497
|
+
source.id,
|
|
498
|
+
source.name || source.id,
|
|
499
|
+
source.base_url || '',
|
|
500
|
+
source.source_type || 'registry',
|
|
501
|
+
source.last_ingested_at || new Date().toISOString(),
|
|
502
|
+
this.stringifyJson(source.metadata || {}, {})
|
|
503
|
+
]);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
upsertRegistryRepo(repo) {
|
|
507
|
+
const sql = `
|
|
508
|
+
INSERT INTO registry_repos (
|
|
509
|
+
id, source_id, repo_id, namespace, canonical_model_id, display_name, url, license,
|
|
510
|
+
gated, requires_auth, downloads, likes, tags, tasks, modalities, last_modified,
|
|
511
|
+
sha, metadata, updated_at
|
|
512
|
+
)
|
|
513
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
514
|
+
ON CONFLICT(source_id, repo_id) DO UPDATE SET
|
|
515
|
+
namespace = excluded.namespace,
|
|
516
|
+
canonical_model_id = excluded.canonical_model_id,
|
|
517
|
+
display_name = excluded.display_name,
|
|
518
|
+
url = excluded.url,
|
|
519
|
+
license = excluded.license,
|
|
520
|
+
gated = excluded.gated,
|
|
521
|
+
requires_auth = excluded.requires_auth,
|
|
522
|
+
downloads = excluded.downloads,
|
|
523
|
+
likes = excluded.likes,
|
|
524
|
+
tags = excluded.tags,
|
|
525
|
+
tasks = excluded.tasks,
|
|
526
|
+
modalities = excluded.modalities,
|
|
527
|
+
last_modified = excluded.last_modified,
|
|
528
|
+
sha = excluded.sha,
|
|
529
|
+
metadata = excluded.metadata,
|
|
530
|
+
updated_at = CURRENT_TIMESTAMP
|
|
531
|
+
`;
|
|
532
|
+
|
|
533
|
+
const repoId = repo.repo_id || repo.id;
|
|
534
|
+
this.run(sql, [
|
|
535
|
+
repo.id,
|
|
536
|
+
repo.source_id,
|
|
537
|
+
repoId,
|
|
538
|
+
repo.namespace || '',
|
|
539
|
+
repo.canonical_model_id || repoId,
|
|
540
|
+
repo.display_name || repo.name || repoId,
|
|
541
|
+
repo.url || '',
|
|
542
|
+
repo.license || 'unknown',
|
|
543
|
+
repo.gated ? 1 : 0,
|
|
544
|
+
repo.requires_auth ? 1 : 0,
|
|
545
|
+
repo.downloads || 0,
|
|
546
|
+
repo.likes || 0,
|
|
547
|
+
this.stringifyJson(repo.tags || [], []),
|
|
548
|
+
this.stringifyJson(repo.tasks || [], []),
|
|
549
|
+
this.stringifyJson(repo.modalities || ['text'], ['text']),
|
|
550
|
+
repo.last_modified || '',
|
|
551
|
+
repo.sha || '',
|
|
552
|
+
this.stringifyJson(repo.metadata || {}, {})
|
|
553
|
+
]);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
upsertModelArtifact(artifact) {
|
|
557
|
+
const sql = `
|
|
558
|
+
INSERT INTO model_artifacts (
|
|
559
|
+
id, source_id, repo_key, repo_id, canonical_model_id, artifact_name, filename,
|
|
560
|
+
format, quantization, precision, parameter_count_b, active_parameter_count_b,
|
|
561
|
+
size_bytes, size_gb, context_length, runtime_support, tasks, modalities,
|
|
562
|
+
download_url, install_command, sha256, etag, license, gated, requires_auth,
|
|
563
|
+
downloads, likes, updated_at, metadata, refreshed_at
|
|
564
|
+
)
|
|
565
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
566
|
+
ON CONFLICT(source_id, repo_key, artifact_name) DO UPDATE SET
|
|
567
|
+
filename = excluded.filename,
|
|
568
|
+
format = excluded.format,
|
|
569
|
+
quantization = excluded.quantization,
|
|
570
|
+
precision = excluded.precision,
|
|
571
|
+
parameter_count_b = excluded.parameter_count_b,
|
|
572
|
+
active_parameter_count_b = excluded.active_parameter_count_b,
|
|
573
|
+
size_bytes = excluded.size_bytes,
|
|
574
|
+
size_gb = excluded.size_gb,
|
|
575
|
+
context_length = excluded.context_length,
|
|
576
|
+
runtime_support = excluded.runtime_support,
|
|
577
|
+
tasks = excluded.tasks,
|
|
578
|
+
modalities = excluded.modalities,
|
|
579
|
+
download_url = excluded.download_url,
|
|
580
|
+
install_command = excluded.install_command,
|
|
581
|
+
sha256 = excluded.sha256,
|
|
582
|
+
etag = excluded.etag,
|
|
583
|
+
license = excluded.license,
|
|
584
|
+
gated = excluded.gated,
|
|
585
|
+
requires_auth = excluded.requires_auth,
|
|
586
|
+
downloads = excluded.downloads,
|
|
587
|
+
likes = excluded.likes,
|
|
588
|
+
updated_at = excluded.updated_at,
|
|
589
|
+
metadata = excluded.metadata,
|
|
590
|
+
refreshed_at = CURRENT_TIMESTAMP
|
|
591
|
+
`;
|
|
592
|
+
|
|
593
|
+
const sizeBytes = Number(artifact.size_bytes);
|
|
594
|
+
const sizeGB = Number(artifact.size_gb);
|
|
595
|
+
const repoId = artifact.repo_id || artifact.repo_key;
|
|
596
|
+
|
|
597
|
+
this.run(sql, [
|
|
598
|
+
artifact.id,
|
|
599
|
+
artifact.source_id,
|
|
600
|
+
artifact.repo_key,
|
|
601
|
+
repoId,
|
|
602
|
+
artifact.canonical_model_id || repoId,
|
|
603
|
+
artifact.artifact_name || artifact.filename || repoId,
|
|
604
|
+
artifact.filename || '',
|
|
605
|
+
artifact.format || 'unknown',
|
|
606
|
+
artifact.quantization || '',
|
|
607
|
+
artifact.precision || '',
|
|
608
|
+
artifact.parameter_count_b || null,
|
|
609
|
+
artifact.active_parameter_count_b || null,
|
|
610
|
+
Number.isFinite(sizeBytes) && sizeBytes > 0 ? sizeBytes : null,
|
|
611
|
+
Number.isFinite(sizeGB) && sizeGB > 0 ? sizeGB : null,
|
|
612
|
+
artifact.context_length || null,
|
|
613
|
+
this.stringifyJson(artifact.runtime_support || [], []),
|
|
614
|
+
this.stringifyJson(artifact.tasks || [], []),
|
|
615
|
+
this.stringifyJson(artifact.modalities || ['text'], ['text']),
|
|
616
|
+
artifact.download_url || '',
|
|
617
|
+
artifact.install_command || '',
|
|
618
|
+
artifact.sha256 || '',
|
|
619
|
+
artifact.etag || '',
|
|
620
|
+
artifact.license || 'unknown',
|
|
621
|
+
artifact.gated ? 1 : 0,
|
|
622
|
+
artifact.requires_auth ? 1 : 0,
|
|
623
|
+
artifact.downloads || 0,
|
|
624
|
+
artifact.likes || 0,
|
|
625
|
+
artifact.updated_at || '',
|
|
626
|
+
this.stringifyJson(artifact.metadata || {}, {})
|
|
627
|
+
]);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
searchModelArtifacts(query = '', filters = {}) {
|
|
631
|
+
let sql = `
|
|
632
|
+
SELECT
|
|
633
|
+
a.*,
|
|
634
|
+
s.name as source_name,
|
|
635
|
+
s.base_url as source_base_url,
|
|
636
|
+
r.display_name as repo_display_name,
|
|
637
|
+
r.url as repo_url
|
|
638
|
+
FROM model_artifacts a
|
|
639
|
+
JOIN registry_sources s ON s.id = a.source_id
|
|
640
|
+
JOIN registry_repos r ON r.id = a.repo_key
|
|
641
|
+
WHERE 1=1
|
|
642
|
+
`;
|
|
643
|
+
const params = [];
|
|
644
|
+
|
|
645
|
+
if (query) {
|
|
646
|
+
sql += ` AND (
|
|
647
|
+
a.canonical_model_id LIKE ? OR
|
|
648
|
+
a.artifact_name LIKE ? OR
|
|
649
|
+
a.filename LIKE ? OR
|
|
650
|
+
a.repo_id LIKE ? OR
|
|
651
|
+
r.display_name LIKE ?
|
|
652
|
+
)`;
|
|
653
|
+
const pattern = `%${query}%`;
|
|
654
|
+
params.push(pattern, pattern, pattern, pattern, pattern);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (filters.source) {
|
|
658
|
+
sql += ` AND a.source_id = ?`;
|
|
659
|
+
params.push(filters.source);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (filters.format) {
|
|
663
|
+
sql += ` AND a.format = ?`;
|
|
664
|
+
params.push(String(filters.format).toLowerCase());
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (filters.quantization) {
|
|
668
|
+
sql += ` AND UPPER(a.quantization) = ?`;
|
|
669
|
+
params.push(String(filters.quantization).toUpperCase());
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (filters.runtime && !['auto', 'all', '*'].includes(String(filters.runtime).toLowerCase())) {
|
|
673
|
+
// Escape LIKE wildcards so a runtime value (e.g. "lla_a") can't act as
|
|
674
|
+
// a pattern and over-match (it would otherwise match "llama").
|
|
675
|
+
const runtimeNeedle = String(filters.runtime).replace(/[\\%_]/g, '\\$&');
|
|
676
|
+
sql += ` AND a.runtime_support LIKE ? ESCAPE '\\'`;
|
|
677
|
+
params.push(`%"${runtimeNeedle}"%`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (filters.maxSizeGB) {
|
|
681
|
+
sql += ` AND (a.size_gb IS NULL OR a.size_gb <= ?)`;
|
|
682
|
+
params.push(filters.maxSizeGB);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (filters.minParamsB) {
|
|
686
|
+
sql += ` AND (a.parameter_count_b IS NULL OR a.parameter_count_b >= ?)`;
|
|
687
|
+
params.push(filters.minParamsB);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (filters.maxParamsB) {
|
|
691
|
+
sql += ` AND (a.parameter_count_b IS NULL OR a.parameter_count_b <= ?)`;
|
|
692
|
+
params.push(filters.maxParamsB);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (filters.localOnly) {
|
|
696
|
+
sql += ` AND a.requires_auth = 0 AND a.gated = 0`;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
sql += ` ORDER BY a.downloads DESC, a.likes DESC, a.size_gb ASC`;
|
|
700
|
+
|
|
701
|
+
if (filters.limit) {
|
|
702
|
+
sql += ` LIMIT ?`;
|
|
703
|
+
params.push(filters.limit);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return this.all(sql, params).map((row) => ({
|
|
707
|
+
...row,
|
|
708
|
+
runtime_support: this.parseJson(row.runtime_support, []),
|
|
709
|
+
tasks: this.parseJson(row.tasks, []),
|
|
710
|
+
modalities: this.parseJson(row.modalities, ['text']),
|
|
711
|
+
metadata: this.parseJson(row.metadata, {})
|
|
712
|
+
}));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
getRegistryStats() {
|
|
716
|
+
return {
|
|
717
|
+
sources: this.get(`SELECT COUNT(*) as count FROM registry_sources`)?.count || 0,
|
|
718
|
+
repos: this.get(`SELECT COUNT(*) as count FROM registry_repos`)?.count || 0,
|
|
719
|
+
artifacts: this.get(`SELECT COUNT(*) as count FROM model_artifacts`)?.count || 0,
|
|
720
|
+
bySource: this.all(`
|
|
721
|
+
SELECT source_id, COUNT(*) as artifact_count
|
|
722
|
+
FROM model_artifacts
|
|
723
|
+
GROUP BY source_id
|
|
724
|
+
ORDER BY artifact_count DESC
|
|
725
|
+
`),
|
|
726
|
+
byFormat: this.all(`
|
|
727
|
+
SELECT format, COUNT(*) as artifact_count
|
|
728
|
+
FROM model_artifacts
|
|
729
|
+
GROUP BY format
|
|
730
|
+
ORDER BY artifact_count DESC
|
|
731
|
+
`)
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async seedRegistryFromPackagedSnapshotIfNeeded() {
|
|
736
|
+
if (!this.seedDbPath || !fs.existsSync(this.seedDbPath)) return false;
|
|
737
|
+
if (path.resolve(this.dbPath) === path.resolve(this.seedDbPath)) return false;
|
|
738
|
+
|
|
739
|
+
const currentArtifacts = this.get(`SELECT COUNT(*) as count FROM model_artifacts`)?.count || 0;
|
|
740
|
+
if (currentArtifacts > 0) return false;
|
|
741
|
+
|
|
742
|
+
const seed = new ModelDatabase({
|
|
743
|
+
dbPath: this.seedDbPath,
|
|
744
|
+
seedDbPath: this.seedDbPath,
|
|
745
|
+
disableRegistrySeedImport: true
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
await seed.initialize();
|
|
749
|
+
try {
|
|
750
|
+
const seedArtifacts = seed.get(`SELECT COUNT(*) as count FROM model_artifacts`)?.count || 0;
|
|
751
|
+
if (seedArtifacts === 0) return false;
|
|
752
|
+
|
|
753
|
+
const sources = seed.all(`SELECT * FROM registry_sources`);
|
|
754
|
+
const repos = seed.all(`SELECT * FROM registry_repos`);
|
|
755
|
+
const artifacts = seed.all(`SELECT * FROM model_artifacts`);
|
|
756
|
+
|
|
757
|
+
this.beginBatch();
|
|
758
|
+
try {
|
|
759
|
+
for (const source of sources) {
|
|
760
|
+
this.upsertRegistrySource({
|
|
761
|
+
...source,
|
|
762
|
+
metadata: this.parseJson(source.metadata, {})
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
for (const repo of repos) {
|
|
767
|
+
this.upsertRegistryRepo({
|
|
768
|
+
...repo,
|
|
769
|
+
gated: Boolean(repo.gated),
|
|
770
|
+
requires_auth: Boolean(repo.requires_auth),
|
|
771
|
+
tags: this.parseJson(repo.tags, []),
|
|
772
|
+
tasks: this.parseJson(repo.tasks, []),
|
|
773
|
+
modalities: this.parseJson(repo.modalities, ['text']),
|
|
774
|
+
metadata: this.parseJson(repo.metadata, {})
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
for (const artifact of artifacts) {
|
|
779
|
+
this.upsertModelArtifact({
|
|
780
|
+
...artifact,
|
|
781
|
+
gated: Boolean(artifact.gated),
|
|
782
|
+
requires_auth: Boolean(artifact.requires_auth),
|
|
783
|
+
runtime_support: this.parseJson(artifact.runtime_support, []),
|
|
784
|
+
tasks: this.parseJson(artifact.tasks, []),
|
|
785
|
+
modalities: this.parseJson(artifact.modalities, ['text']),
|
|
786
|
+
metadata: this.parseJson(artifact.metadata, {})
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
} finally {
|
|
790
|
+
this.endBatch();
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return true;
|
|
794
|
+
} finally {
|
|
795
|
+
seed.close();
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
368
799
|
// ==================== SEARCH OPERATIONS ====================
|
|
369
800
|
|
|
370
801
|
/**
|
|
@@ -738,12 +1169,31 @@ class ModelDatabase {
|
|
|
738
1169
|
* Clear all data
|
|
739
1170
|
*/
|
|
740
1171
|
clear() {
|
|
1172
|
+
// The registry's Ollama source is derived from the local Ollama catalog.
|
|
1173
|
+
// Clear only that source so a classic Ollama sync does not erase HF/GPT4All data.
|
|
1174
|
+
this.clearRegistrySource('ollama');
|
|
741
1175
|
this.run(`DELETE FROM benchmarks`);
|
|
742
1176
|
this.run(`DELETE FROM variants`);
|
|
743
1177
|
this.run(`DELETE FROM models`);
|
|
744
1178
|
this.run(`DELETE FROM sync_meta`);
|
|
745
1179
|
}
|
|
746
1180
|
|
|
1181
|
+
/**
|
|
1182
|
+
* Clear registry data. When sourceId is provided, only that source is removed.
|
|
1183
|
+
*/
|
|
1184
|
+
clearRegistrySource(sourceId = null) {
|
|
1185
|
+
if (sourceId) {
|
|
1186
|
+
this.run(`DELETE FROM model_artifacts WHERE source_id = ?`, [sourceId]);
|
|
1187
|
+
this.run(`DELETE FROM registry_repos WHERE source_id = ?`, [sourceId]);
|
|
1188
|
+
this.run(`DELETE FROM registry_sources WHERE id = ?`, [sourceId]);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
this.run(`DELETE FROM model_artifacts`);
|
|
1193
|
+
this.run(`DELETE FROM registry_repos`);
|
|
1194
|
+
this.run(`DELETE FROM registry_sources`);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
747
1197
|
/**
|
|
748
1198
|
* Close database connection
|
|
749
1199
|
*/
|