llm-checker 3.6.1 → 3.7.4
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 +45 -8
- package/bin/enhanced_cli.js +407 -5
- package/bin/mcp-server.mjs +5 -0
- package/package.json +7 -2
- package/src/data/model-database.js +452 -0
- package/src/data/registry-ingestors.js +765 -0
- package/src/data/registry-recommender.js +632 -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 +85 -39
- 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,18 @@ 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_size ON model_artifacts(size_gb);
|
|
231
|
+
CREATE INDEX IF NOT EXISTS idx_model_artifacts_downloads ON model_artifacts(downloads DESC);
|
|
232
|
+
-- Drop a dead index from older DBs: runtime_support is a JSON blob only
|
|
233
|
+
-- queried with LIKE, so a B-tree index on it is never used.
|
|
234
|
+
DROP INDEX IF EXISTS idx_model_artifacts_runtime;
|
|
139
235
|
`;
|
|
140
236
|
|
|
141
237
|
if (this.useBetterSqlite) {
|
|
@@ -365,6 +461,343 @@ class ModelDatabase {
|
|
|
365
461
|
]);
|
|
366
462
|
}
|
|
367
463
|
|
|
464
|
+
// ==================== MODEL REGISTRY OPERATIONS ====================
|
|
465
|
+
|
|
466
|
+
stringifyJson(value, fallback) {
|
|
467
|
+
const safeValue = value === undefined ? fallback : value;
|
|
468
|
+
try {
|
|
469
|
+
return JSON.stringify(safeValue);
|
|
470
|
+
} catch {
|
|
471
|
+
return JSON.stringify(fallback);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
parseJson(value, fallback) {
|
|
476
|
+
if (!value) return fallback;
|
|
477
|
+
try {
|
|
478
|
+
const parsed = JSON.parse(value);
|
|
479
|
+
return parsed;
|
|
480
|
+
} catch {
|
|
481
|
+
return fallback;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
upsertRegistrySource(source) {
|
|
486
|
+
const sql = `
|
|
487
|
+
INSERT INTO registry_sources (id, name, base_url, source_type, last_ingested_at, metadata, updated_at)
|
|
488
|
+
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
489
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
490
|
+
name = excluded.name,
|
|
491
|
+
base_url = excluded.base_url,
|
|
492
|
+
source_type = excluded.source_type,
|
|
493
|
+
last_ingested_at = excluded.last_ingested_at,
|
|
494
|
+
metadata = excluded.metadata,
|
|
495
|
+
updated_at = CURRENT_TIMESTAMP
|
|
496
|
+
`;
|
|
497
|
+
|
|
498
|
+
this.run(sql, [
|
|
499
|
+
source.id,
|
|
500
|
+
source.name || source.id,
|
|
501
|
+
source.base_url || '',
|
|
502
|
+
source.source_type || 'registry',
|
|
503
|
+
source.last_ingested_at || new Date().toISOString(),
|
|
504
|
+
this.stringifyJson(source.metadata || {}, {})
|
|
505
|
+
]);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
upsertRegistryRepo(repo) {
|
|
509
|
+
const sql = `
|
|
510
|
+
INSERT INTO registry_repos (
|
|
511
|
+
id, source_id, repo_id, namespace, canonical_model_id, display_name, url, license,
|
|
512
|
+
gated, requires_auth, downloads, likes, tags, tasks, modalities, last_modified,
|
|
513
|
+
sha, metadata, updated_at
|
|
514
|
+
)
|
|
515
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
516
|
+
ON CONFLICT(source_id, repo_id) DO UPDATE SET
|
|
517
|
+
namespace = excluded.namespace,
|
|
518
|
+
canonical_model_id = excluded.canonical_model_id,
|
|
519
|
+
display_name = excluded.display_name,
|
|
520
|
+
url = excluded.url,
|
|
521
|
+
license = excluded.license,
|
|
522
|
+
gated = excluded.gated,
|
|
523
|
+
requires_auth = excluded.requires_auth,
|
|
524
|
+
downloads = excluded.downloads,
|
|
525
|
+
likes = excluded.likes,
|
|
526
|
+
tags = excluded.tags,
|
|
527
|
+
tasks = excluded.tasks,
|
|
528
|
+
modalities = excluded.modalities,
|
|
529
|
+
last_modified = excluded.last_modified,
|
|
530
|
+
sha = excluded.sha,
|
|
531
|
+
metadata = excluded.metadata,
|
|
532
|
+
updated_at = CURRENT_TIMESTAMP
|
|
533
|
+
`;
|
|
534
|
+
|
|
535
|
+
const repoId = repo.repo_id || repo.id;
|
|
536
|
+
this.run(sql, [
|
|
537
|
+
repo.id,
|
|
538
|
+
repo.source_id,
|
|
539
|
+
repoId,
|
|
540
|
+
repo.namespace || '',
|
|
541
|
+
repo.canonical_model_id || repoId,
|
|
542
|
+
repo.display_name || repo.name || repoId,
|
|
543
|
+
repo.url || '',
|
|
544
|
+
repo.license || 'unknown',
|
|
545
|
+
repo.gated ? 1 : 0,
|
|
546
|
+
repo.requires_auth ? 1 : 0,
|
|
547
|
+
repo.downloads || 0,
|
|
548
|
+
repo.likes || 0,
|
|
549
|
+
this.stringifyJson(repo.tags || [], []),
|
|
550
|
+
this.stringifyJson(repo.tasks || [], []),
|
|
551
|
+
this.stringifyJson(repo.modalities || ['text'], ['text']),
|
|
552
|
+
repo.last_modified || '',
|
|
553
|
+
repo.sha || '',
|
|
554
|
+
this.stringifyJson(repo.metadata || {}, {})
|
|
555
|
+
]);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
upsertModelArtifact(artifact) {
|
|
559
|
+
const sql = `
|
|
560
|
+
INSERT INTO model_artifacts (
|
|
561
|
+
id, source_id, repo_key, repo_id, canonical_model_id, artifact_name, filename,
|
|
562
|
+
format, quantization, precision, parameter_count_b, active_parameter_count_b,
|
|
563
|
+
size_bytes, size_gb, context_length, runtime_support, tasks, modalities,
|
|
564
|
+
download_url, install_command, sha256, etag, license, gated, requires_auth,
|
|
565
|
+
downloads, likes, updated_at, metadata, refreshed_at
|
|
566
|
+
)
|
|
567
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
568
|
+
ON CONFLICT(source_id, repo_key, artifact_name) DO UPDATE SET
|
|
569
|
+
filename = excluded.filename,
|
|
570
|
+
format = excluded.format,
|
|
571
|
+
quantization = excluded.quantization,
|
|
572
|
+
precision = excluded.precision,
|
|
573
|
+
parameter_count_b = excluded.parameter_count_b,
|
|
574
|
+
active_parameter_count_b = excluded.active_parameter_count_b,
|
|
575
|
+
size_bytes = excluded.size_bytes,
|
|
576
|
+
size_gb = excluded.size_gb,
|
|
577
|
+
context_length = excluded.context_length,
|
|
578
|
+
runtime_support = excluded.runtime_support,
|
|
579
|
+
tasks = excluded.tasks,
|
|
580
|
+
modalities = excluded.modalities,
|
|
581
|
+
download_url = excluded.download_url,
|
|
582
|
+
install_command = excluded.install_command,
|
|
583
|
+
sha256 = excluded.sha256,
|
|
584
|
+
etag = excluded.etag,
|
|
585
|
+
license = excluded.license,
|
|
586
|
+
gated = excluded.gated,
|
|
587
|
+
requires_auth = excluded.requires_auth,
|
|
588
|
+
downloads = excluded.downloads,
|
|
589
|
+
likes = excluded.likes,
|
|
590
|
+
updated_at = excluded.updated_at,
|
|
591
|
+
metadata = excluded.metadata,
|
|
592
|
+
refreshed_at = CURRENT_TIMESTAMP
|
|
593
|
+
`;
|
|
594
|
+
|
|
595
|
+
const sizeBytes = Number(artifact.size_bytes);
|
|
596
|
+
const sizeGB = Number(artifact.size_gb);
|
|
597
|
+
const repoId = artifact.repo_id || artifact.repo_key;
|
|
598
|
+
|
|
599
|
+
this.run(sql, [
|
|
600
|
+
artifact.id,
|
|
601
|
+
artifact.source_id,
|
|
602
|
+
artifact.repo_key,
|
|
603
|
+
repoId,
|
|
604
|
+
artifact.canonical_model_id || repoId,
|
|
605
|
+
artifact.artifact_name || artifact.filename || repoId,
|
|
606
|
+
artifact.filename || '',
|
|
607
|
+
artifact.format || 'unknown',
|
|
608
|
+
artifact.quantization || '',
|
|
609
|
+
artifact.precision || '',
|
|
610
|
+
artifact.parameter_count_b || null,
|
|
611
|
+
artifact.active_parameter_count_b || null,
|
|
612
|
+
Number.isFinite(sizeBytes) && sizeBytes > 0 ? sizeBytes : null,
|
|
613
|
+
Number.isFinite(sizeGB) && sizeGB > 0 ? sizeGB : null,
|
|
614
|
+
artifact.context_length || null,
|
|
615
|
+
this.stringifyJson(artifact.runtime_support || [], []),
|
|
616
|
+
this.stringifyJson(artifact.tasks || [], []),
|
|
617
|
+
this.stringifyJson(artifact.modalities || ['text'], ['text']),
|
|
618
|
+
artifact.download_url || '',
|
|
619
|
+
artifact.install_command || '',
|
|
620
|
+
artifact.sha256 || '',
|
|
621
|
+
artifact.etag || '',
|
|
622
|
+
artifact.license || 'unknown',
|
|
623
|
+
artifact.gated ? 1 : 0,
|
|
624
|
+
artifact.requires_auth ? 1 : 0,
|
|
625
|
+
artifact.downloads || 0,
|
|
626
|
+
artifact.likes || 0,
|
|
627
|
+
artifact.updated_at || '',
|
|
628
|
+
this.stringifyJson(artifact.metadata || {}, {})
|
|
629
|
+
]);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
searchModelArtifacts(query = '', filters = {}) {
|
|
633
|
+
let sql = `
|
|
634
|
+
SELECT
|
|
635
|
+
a.*,
|
|
636
|
+
s.name as source_name,
|
|
637
|
+
s.base_url as source_base_url,
|
|
638
|
+
r.display_name as repo_display_name,
|
|
639
|
+
r.url as repo_url
|
|
640
|
+
FROM model_artifacts a
|
|
641
|
+
JOIN registry_sources s ON s.id = a.source_id
|
|
642
|
+
JOIN registry_repos r ON r.id = a.repo_key
|
|
643
|
+
WHERE 1=1
|
|
644
|
+
`;
|
|
645
|
+
const params = [];
|
|
646
|
+
|
|
647
|
+
if (query) {
|
|
648
|
+
sql += ` AND (
|
|
649
|
+
a.canonical_model_id LIKE ? OR
|
|
650
|
+
a.artifact_name LIKE ? OR
|
|
651
|
+
a.filename LIKE ? OR
|
|
652
|
+
a.repo_id LIKE ? OR
|
|
653
|
+
r.display_name LIKE ?
|
|
654
|
+
)`;
|
|
655
|
+
const pattern = `%${query}%`;
|
|
656
|
+
params.push(pattern, pattern, pattern, pattern, pattern);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (filters.source) {
|
|
660
|
+
sql += ` AND a.source_id = ?`;
|
|
661
|
+
params.push(filters.source);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (filters.format) {
|
|
665
|
+
sql += ` AND a.format = ?`;
|
|
666
|
+
params.push(String(filters.format).toLowerCase());
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (filters.quantization) {
|
|
670
|
+
sql += ` AND UPPER(a.quantization) = ?`;
|
|
671
|
+
params.push(String(filters.quantization).toUpperCase());
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (filters.runtime && !['auto', 'all', '*'].includes(String(filters.runtime).toLowerCase())) {
|
|
675
|
+
// Escape LIKE wildcards so a runtime value (e.g. "lla_a") can't act as
|
|
676
|
+
// a pattern and over-match (it would otherwise match "llama").
|
|
677
|
+
const runtimeNeedle = String(filters.runtime).replace(/[\\%_]/g, '\\$&');
|
|
678
|
+
sql += ` AND a.runtime_support LIKE ? ESCAPE '\\'`;
|
|
679
|
+
params.push(`%"${runtimeNeedle}"%`);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (filters.maxSizeGB) {
|
|
683
|
+
sql += ` AND (a.size_gb IS NULL OR a.size_gb <= ?)`;
|
|
684
|
+
params.push(filters.maxSizeGB);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (filters.minParamsB) {
|
|
688
|
+
sql += ` AND (a.parameter_count_b IS NULL OR a.parameter_count_b >= ?)`;
|
|
689
|
+
params.push(filters.minParamsB);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (filters.maxParamsB) {
|
|
693
|
+
sql += ` AND (a.parameter_count_b IS NULL OR a.parameter_count_b <= ?)`;
|
|
694
|
+
params.push(filters.maxParamsB);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (filters.localOnly) {
|
|
698
|
+
sql += ` AND a.requires_auth = 0 AND a.gated = 0`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
sql += ` ORDER BY a.downloads DESC, a.likes DESC, a.size_gb ASC`;
|
|
702
|
+
|
|
703
|
+
if (filters.limit) {
|
|
704
|
+
sql += ` LIMIT ?`;
|
|
705
|
+
params.push(filters.limit);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return this.all(sql, params).map((row) => ({
|
|
709
|
+
...row,
|
|
710
|
+
runtime_support: this.parseJson(row.runtime_support, []),
|
|
711
|
+
tasks: this.parseJson(row.tasks, []),
|
|
712
|
+
modalities: this.parseJson(row.modalities, ['text']),
|
|
713
|
+
metadata: this.parseJson(row.metadata, {})
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
getRegistryStats() {
|
|
718
|
+
return {
|
|
719
|
+
sources: this.get(`SELECT COUNT(*) as count FROM registry_sources`)?.count || 0,
|
|
720
|
+
repos: this.get(`SELECT COUNT(*) as count FROM registry_repos`)?.count || 0,
|
|
721
|
+
artifacts: this.get(`SELECT COUNT(*) as count FROM model_artifacts`)?.count || 0,
|
|
722
|
+
bySource: this.all(`
|
|
723
|
+
SELECT source_id, COUNT(*) as artifact_count
|
|
724
|
+
FROM model_artifacts
|
|
725
|
+
GROUP BY source_id
|
|
726
|
+
ORDER BY artifact_count DESC
|
|
727
|
+
`),
|
|
728
|
+
byFormat: this.all(`
|
|
729
|
+
SELECT format, COUNT(*) as artifact_count
|
|
730
|
+
FROM model_artifacts
|
|
731
|
+
GROUP BY format
|
|
732
|
+
ORDER BY artifact_count DESC
|
|
733
|
+
`)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async seedRegistryFromPackagedSnapshotIfNeeded() {
|
|
738
|
+
if (!this.seedDbPath || !fs.existsSync(this.seedDbPath)) return false;
|
|
739
|
+
if (path.resolve(this.dbPath) === path.resolve(this.seedDbPath)) return false;
|
|
740
|
+
|
|
741
|
+
const currentArtifacts = this.get(`SELECT COUNT(*) as count FROM model_artifacts`)?.count || 0;
|
|
742
|
+
if (currentArtifacts > 0) return false;
|
|
743
|
+
|
|
744
|
+
const seed = new ModelDatabase({
|
|
745
|
+
dbPath: this.seedDbPath,
|
|
746
|
+
seedDbPath: this.seedDbPath,
|
|
747
|
+
disableRegistrySeedImport: true
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
await seed.initialize();
|
|
751
|
+
try {
|
|
752
|
+
const seedArtifacts = seed.get(`SELECT COUNT(*) as count FROM model_artifacts`)?.count || 0;
|
|
753
|
+
if (seedArtifacts === 0) return false;
|
|
754
|
+
|
|
755
|
+
const sources = seed.all(`SELECT * FROM registry_sources`);
|
|
756
|
+
const repos = seed.all(`SELECT * FROM registry_repos`);
|
|
757
|
+
const artifacts = seed.all(`SELECT * FROM model_artifacts`);
|
|
758
|
+
|
|
759
|
+
this.beginBatch();
|
|
760
|
+
try {
|
|
761
|
+
for (const source of sources) {
|
|
762
|
+
this.upsertRegistrySource({
|
|
763
|
+
...source,
|
|
764
|
+
metadata: this.parseJson(source.metadata, {})
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
for (const repo of repos) {
|
|
769
|
+
this.upsertRegistryRepo({
|
|
770
|
+
...repo,
|
|
771
|
+
gated: Boolean(repo.gated),
|
|
772
|
+
requires_auth: Boolean(repo.requires_auth),
|
|
773
|
+
tags: this.parseJson(repo.tags, []),
|
|
774
|
+
tasks: this.parseJson(repo.tasks, []),
|
|
775
|
+
modalities: this.parseJson(repo.modalities, ['text']),
|
|
776
|
+
metadata: this.parseJson(repo.metadata, {})
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
for (const artifact of artifacts) {
|
|
781
|
+
this.upsertModelArtifact({
|
|
782
|
+
...artifact,
|
|
783
|
+
gated: Boolean(artifact.gated),
|
|
784
|
+
requires_auth: Boolean(artifact.requires_auth),
|
|
785
|
+
runtime_support: this.parseJson(artifact.runtime_support, []),
|
|
786
|
+
tasks: this.parseJson(artifact.tasks, []),
|
|
787
|
+
modalities: this.parseJson(artifact.modalities, ['text']),
|
|
788
|
+
metadata: this.parseJson(artifact.metadata, {})
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
} finally {
|
|
792
|
+
this.endBatch();
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return true;
|
|
796
|
+
} finally {
|
|
797
|
+
seed.close();
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
368
801
|
// ==================== SEARCH OPERATIONS ====================
|
|
369
802
|
|
|
370
803
|
/**
|
|
@@ -738,12 +1171,31 @@ class ModelDatabase {
|
|
|
738
1171
|
* Clear all data
|
|
739
1172
|
*/
|
|
740
1173
|
clear() {
|
|
1174
|
+
// The registry's Ollama source is derived from the local Ollama catalog.
|
|
1175
|
+
// Clear only that source so a classic Ollama sync does not erase HF/GPT4All data.
|
|
1176
|
+
this.clearRegistrySource('ollama');
|
|
741
1177
|
this.run(`DELETE FROM benchmarks`);
|
|
742
1178
|
this.run(`DELETE FROM variants`);
|
|
743
1179
|
this.run(`DELETE FROM models`);
|
|
744
1180
|
this.run(`DELETE FROM sync_meta`);
|
|
745
1181
|
}
|
|
746
1182
|
|
|
1183
|
+
/**
|
|
1184
|
+
* Clear registry data. When sourceId is provided, only that source is removed.
|
|
1185
|
+
*/
|
|
1186
|
+
clearRegistrySource(sourceId = null) {
|
|
1187
|
+
if (sourceId) {
|
|
1188
|
+
this.run(`DELETE FROM model_artifacts WHERE source_id = ?`, [sourceId]);
|
|
1189
|
+
this.run(`DELETE FROM registry_repos WHERE source_id = ?`, [sourceId]);
|
|
1190
|
+
this.run(`DELETE FROM registry_sources WHERE id = ?`, [sourceId]);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
this.run(`DELETE FROM model_artifacts`);
|
|
1195
|
+
this.run(`DELETE FROM registry_repos`);
|
|
1196
|
+
this.run(`DELETE FROM registry_sources`);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
747
1199
|
/**
|
|
748
1200
|
* Close database connection
|
|
749
1201
|
*/
|