llm-checker 3.1.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/LICENSE +21 -0
- package/README.md +418 -0
- package/analyzer/compatibility.js +584 -0
- package/analyzer/performance.js +505 -0
- package/bin/CLAUDE.md +12 -0
- package/bin/enhanced_cli.js +3118 -0
- package/bin/test-deterministic.js +41 -0
- package/package.json +96 -0
- package/src/CLAUDE.md +12 -0
- package/src/ai/intelligent-selector.js +615 -0
- package/src/ai/model-selector.js +312 -0
- package/src/ai/multi-objective-selector.js +820 -0
- package/src/commands/check.js +58 -0
- package/src/data/CLAUDE.md +11 -0
- package/src/data/model-database.js +637 -0
- package/src/data/sync-manager.js +279 -0
- package/src/hardware/CLAUDE.md +12 -0
- package/src/hardware/backends/CLAUDE.md +11 -0
- package/src/hardware/backends/apple-silicon.js +318 -0
- package/src/hardware/backends/cpu-detector.js +490 -0
- package/src/hardware/backends/cuda-detector.js +417 -0
- package/src/hardware/backends/intel-detector.js +436 -0
- package/src/hardware/backends/rocm-detector.js +440 -0
- package/src/hardware/detector.js +573 -0
- package/src/hardware/pc-optimizer.js +635 -0
- package/src/hardware/specs.js +286 -0
- package/src/hardware/unified-detector.js +442 -0
- package/src/index.js +2289 -0
- package/src/models/CLAUDE.md +17 -0
- package/src/models/ai-check-selector.js +806 -0
- package/src/models/catalog.json +426 -0
- package/src/models/deterministic-selector.js +1145 -0
- package/src/models/expanded_database.js +1142 -0
- package/src/models/intelligent-selector.js +532 -0
- package/src/models/requirements.js +310 -0
- package/src/models/scoring-config.js +57 -0
- package/src/models/scoring-engine.js +715 -0
- package/src/ollama/.cache/README.md +33 -0
- package/src/ollama/CLAUDE.md +24 -0
- package/src/ollama/client.js +438 -0
- package/src/ollama/enhanced-client.js +113 -0
- package/src/ollama/enhanced-scraper.js +634 -0
- package/src/ollama/manager.js +357 -0
- package/src/ollama/native-scraper.js +776 -0
- package/src/plugins/CLAUDE.md +11 -0
- package/src/plugins/examples/custom_model_plugin.js +87 -0
- package/src/plugins/index.js +295 -0
- package/src/utils/CLAUDE.md +11 -0
- package/src/utils/config.js +359 -0
- package/src/utils/formatter.js +315 -0
- package/src/utils/logger.js +272 -0
- package/src/utils/model-classifier.js +167 -0
- package/src/utils/verbose-progress.js +266 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const EnhancedOllamaClient = require('../ollama/enhanced-client');
|
|
2
|
+
|
|
3
|
+
async function checkOllamaIntegration() {
|
|
4
|
+
const ollama = new EnhancedOllamaClient();
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
// Verificar si Ollama está corriendo
|
|
8
|
+
const status = await ollama.getStatus();
|
|
9
|
+
|
|
10
|
+
if (!status.running) {
|
|
11
|
+
console.log('Ollama Status: Not running');
|
|
12
|
+
console.log(' Start with: ollama serve');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log(`Ollama Status: Running (v${status.version})`);
|
|
17
|
+
|
|
18
|
+
// 🆕 Nueva funcionalidad mejorada
|
|
19
|
+
console.log('Analyzing compatibility with enhanced model database...');
|
|
20
|
+
const enhanced = await ollama.getEnhancedCompatibleModels();
|
|
21
|
+
|
|
22
|
+
console.log(`Installed: ${enhanced.installed} total, ${enhanced.compatible} compatible`);
|
|
23
|
+
console.log(`Available: ${enhanced.total_available} models in Ollama library`);
|
|
24
|
+
|
|
25
|
+
// Mostrar modelos compatibles encontrados
|
|
26
|
+
if (enhanced.compatible > 0) {
|
|
27
|
+
console.log('\nCompatible Models Found:');
|
|
28
|
+
enhanced.compatible_models.forEach(model => {
|
|
29
|
+
const matchType = model.match_type === 'exact' ? 'EXACT' : 'PARTIAL';
|
|
30
|
+
console.log(` ${matchType} ${model.name} (Score: ${model.score}/100)`);
|
|
31
|
+
console.log(` Local: ${model.local_name}`);
|
|
32
|
+
console.log(` Pulls: ${model.pulls?.toLocaleString() || 'Unknown'}`);
|
|
33
|
+
console.log(` Install: ${model.installation.ollama}`);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
console.log('\nNo compatible models found in current database');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Mostrar recomendaciones
|
|
40
|
+
if (enhanced.recommendations.length > 0) {
|
|
41
|
+
console.log('\nRecommended popular models:');
|
|
42
|
+
enhanced.recommendations.slice(0, 5).forEach(model => {
|
|
43
|
+
console.log(` ${model.name} (${model.pulls?.toLocaleString() || 'Unknown'} pulls)`);
|
|
44
|
+
console.log(` ${model.description}`);
|
|
45
|
+
console.log(` Install: ${model.installation.ollama}`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Info del cache
|
|
50
|
+
if (enhanced.cache_info) {
|
|
51
|
+
const cached = new Date(enhanced.cache_info.cached_at).toLocaleString();
|
|
52
|
+
console.log(`\nModel data cached at: ${cached}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error checking Ollama integration:', error.message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Feb 12, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #3464 | 10:03 PM | 🔵 | SQL Database Schema - Indexed Model Repository with Benchmarks | ~555 |
|
|
11
|
+
</claude-mem-context>
|
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Database - SQLite storage for Ollama models
|
|
3
|
+
* Provides fast indexed searches across 4000+ models
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
class ModelDatabase {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.dbPath = options.dbPath || path.join(os.homedir(), '.llm-checker', 'models.db');
|
|
13
|
+
this.db = null;
|
|
14
|
+
this.initialized = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize database with schema
|
|
19
|
+
*/
|
|
20
|
+
async initialize() {
|
|
21
|
+
if (this.initialized) return;
|
|
22
|
+
|
|
23
|
+
// Ensure directory exists
|
|
24
|
+
const dbDir = path.dirname(this.dbPath);
|
|
25
|
+
if (!fs.existsSync(dbDir)) {
|
|
26
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Use sql.js (optional dependency)
|
|
30
|
+
let initSqlJs;
|
|
31
|
+
try {
|
|
32
|
+
initSqlJs = require('sql.js');
|
|
33
|
+
} catch (e) {
|
|
34
|
+
throw new Error('sql.js is not installed. Install it with: npm install sql.js');
|
|
35
|
+
}
|
|
36
|
+
const SQL = await initSqlJs();
|
|
37
|
+
|
|
38
|
+
// Load existing database or create new
|
|
39
|
+
if (fs.existsSync(this.dbPath)) {
|
|
40
|
+
const buffer = fs.readFileSync(this.dbPath);
|
|
41
|
+
this.db = new SQL.Database(buffer);
|
|
42
|
+
} else {
|
|
43
|
+
this.db = new SQL.Database();
|
|
44
|
+
}
|
|
45
|
+
this.useBetterSqlite = false;
|
|
46
|
+
|
|
47
|
+
this.createSchema();
|
|
48
|
+
this.initialized = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create database schema
|
|
53
|
+
*/
|
|
54
|
+
createSchema() {
|
|
55
|
+
const schema = `
|
|
56
|
+
-- Main models table
|
|
57
|
+
CREATE TABLE IF NOT EXISTS models (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
name TEXT NOT NULL,
|
|
60
|
+
family TEXT,
|
|
61
|
+
type TEXT DEFAULT 'official',
|
|
62
|
+
description TEXT,
|
|
63
|
+
capabilities TEXT,
|
|
64
|
+
pulls INTEGER DEFAULT 0,
|
|
65
|
+
tags_count INTEGER DEFAULT 0,
|
|
66
|
+
namespace TEXT,
|
|
67
|
+
url TEXT,
|
|
68
|
+
last_updated TEXT,
|
|
69
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
70
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
-- Variants table (each quantization/size combination)
|
|
74
|
+
CREATE TABLE IF NOT EXISTS variants (
|
|
75
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
+
model_id TEXT NOT NULL,
|
|
77
|
+
tag TEXT NOT NULL,
|
|
78
|
+
params_b REAL,
|
|
79
|
+
quant TEXT,
|
|
80
|
+
size_gb REAL,
|
|
81
|
+
context_length INTEGER DEFAULT 4096,
|
|
82
|
+
input_types TEXT DEFAULT '["text"]',
|
|
83
|
+
is_moe INTEGER DEFAULT 0,
|
|
84
|
+
expert_count INTEGER,
|
|
85
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
86
|
+
FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE,
|
|
87
|
+
UNIQUE(model_id, tag)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
-- Benchmarks table (real performance data per hardware)
|
|
91
|
+
CREATE TABLE IF NOT EXISTS benchmarks (
|
|
92
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
93
|
+
variant_id INTEGER NOT NULL,
|
|
94
|
+
hardware_fingerprint TEXT NOT NULL,
|
|
95
|
+
tokens_per_second REAL,
|
|
96
|
+
time_to_first_token REAL,
|
|
97
|
+
memory_used_gb REAL,
|
|
98
|
+
backend TEXT,
|
|
99
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
100
|
+
FOREIGN KEY (variant_id) REFERENCES variants(id) ON DELETE CASCADE
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
-- Sync metadata table
|
|
104
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
105
|
+
key TEXT PRIMARY KEY,
|
|
106
|
+
value TEXT,
|
|
107
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
-- Create indexes for fast searches
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_models_family ON models(family);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_models_pulls ON models(pulls DESC);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_models_type ON models(type);
|
|
114
|
+
CREATE INDEX IF NOT EXISTS idx_variants_params ON variants(params_b);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_variants_size ON variants(size_gb);
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_variants_quant ON variants(quant);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_variants_model ON variants(model_id);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_benchmarks_hardware ON benchmarks(hardware_fingerprint);
|
|
119
|
+
CREATE INDEX IF NOT EXISTS idx_benchmarks_variant ON benchmarks(variant_id);
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
if (this.useBetterSqlite) {
|
|
123
|
+
this.db.exec(schema);
|
|
124
|
+
} else {
|
|
125
|
+
this.db.run(schema);
|
|
126
|
+
this.saveToFile();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Save sql.js database to file
|
|
132
|
+
*/
|
|
133
|
+
saveToFile() {
|
|
134
|
+
if (!this.useBetterSqlite && this.db) {
|
|
135
|
+
const data = this.db.export();
|
|
136
|
+
const buffer = Buffer.from(data);
|
|
137
|
+
fs.writeFileSync(this.dbPath, buffer);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Execute a query (handles both sqlite implementations)
|
|
143
|
+
*/
|
|
144
|
+
run(sql, params = []) {
|
|
145
|
+
if (this.useBetterSqlite) {
|
|
146
|
+
return this.db.prepare(sql).run(...params);
|
|
147
|
+
} else {
|
|
148
|
+
this.db.run(sql, params);
|
|
149
|
+
this.saveToFile();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get all results from a query
|
|
155
|
+
*/
|
|
156
|
+
all(sql, params = []) {
|
|
157
|
+
if (this.useBetterSqlite) {
|
|
158
|
+
return this.db.prepare(sql).all(...params);
|
|
159
|
+
} else {
|
|
160
|
+
const stmt = this.db.prepare(sql);
|
|
161
|
+
stmt.bind(params);
|
|
162
|
+
const results = [];
|
|
163
|
+
while (stmt.step()) {
|
|
164
|
+
results.push(stmt.getAsObject());
|
|
165
|
+
}
|
|
166
|
+
stmt.free();
|
|
167
|
+
return results;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get single result from a query
|
|
173
|
+
*/
|
|
174
|
+
get(sql, params = []) {
|
|
175
|
+
if (this.useBetterSqlite) {
|
|
176
|
+
return this.db.prepare(sql).get(...params);
|
|
177
|
+
} else {
|
|
178
|
+
const results = this.all(sql, params);
|
|
179
|
+
return results.length > 0 ? results[0] : null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ==================== MODEL OPERATIONS ====================
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Insert or update a model
|
|
187
|
+
*/
|
|
188
|
+
upsertModel(model) {
|
|
189
|
+
const sql = `
|
|
190
|
+
INSERT INTO models (id, name, family, type, description, capabilities, pulls, tags_count, namespace, url, last_updated, updated_at)
|
|
191
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
192
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
193
|
+
name = excluded.name,
|
|
194
|
+
family = excluded.family,
|
|
195
|
+
type = excluded.type,
|
|
196
|
+
description = excluded.description,
|
|
197
|
+
capabilities = excluded.capabilities,
|
|
198
|
+
pulls = excluded.pulls,
|
|
199
|
+
tags_count = excluded.tags_count,
|
|
200
|
+
namespace = excluded.namespace,
|
|
201
|
+
url = excluded.url,
|
|
202
|
+
last_updated = excluded.last_updated,
|
|
203
|
+
updated_at = CURRENT_TIMESTAMP
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
this.run(sql, [
|
|
207
|
+
model.id,
|
|
208
|
+
model.name,
|
|
209
|
+
model.family || this.inferFamily(model.id),
|
|
210
|
+
model.type || 'official',
|
|
211
|
+
model.description || '',
|
|
212
|
+
JSON.stringify(model.capabilities || []),
|
|
213
|
+
model.pulls || 0,
|
|
214
|
+
model.tags_count || 0,
|
|
215
|
+
model.namespace || '',
|
|
216
|
+
model.url || `https://ollama.com/library/${model.id}`,
|
|
217
|
+
model.last_updated || ''
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Infer model family from identifier
|
|
223
|
+
*/
|
|
224
|
+
inferFamily(modelId) {
|
|
225
|
+
const id = modelId.toLowerCase();
|
|
226
|
+
|
|
227
|
+
const families = [
|
|
228
|
+
{ pattern: /llama3\.2/, family: 'llama3.2' },
|
|
229
|
+
{ pattern: /llama3\.1/, family: 'llama3.1' },
|
|
230
|
+
{ pattern: /llama3/, family: 'llama3' },
|
|
231
|
+
{ pattern: /llama2/, family: 'llama2' },
|
|
232
|
+
{ pattern: /qwen2\.5/, family: 'qwen2.5' },
|
|
233
|
+
{ pattern: /qwen2/, family: 'qwen2' },
|
|
234
|
+
{ pattern: /qwen/, family: 'qwen' },
|
|
235
|
+
{ pattern: /mistral/, family: 'mistral' },
|
|
236
|
+
{ pattern: /mixtral/, family: 'mixtral' },
|
|
237
|
+
{ pattern: /gemma2/, family: 'gemma2' },
|
|
238
|
+
{ pattern: /gemma/, family: 'gemma' },
|
|
239
|
+
{ pattern: /phi-?3/, family: 'phi3' },
|
|
240
|
+
{ pattern: /phi-?4/, family: 'phi4' },
|
|
241
|
+
{ pattern: /phi/, family: 'phi' },
|
|
242
|
+
{ pattern: /deepseek-?coder/, family: 'deepseek-coder' },
|
|
243
|
+
{ pattern: /deepseek-?r1/, family: 'deepseek-r1' },
|
|
244
|
+
{ pattern: /deepseek/, family: 'deepseek' },
|
|
245
|
+
{ pattern: /codellama/, family: 'codellama' },
|
|
246
|
+
{ pattern: /starcoder/, family: 'starcoder' },
|
|
247
|
+
{ pattern: /tinyllama/, family: 'tinyllama' },
|
|
248
|
+
{ pattern: /llava/, family: 'llava' },
|
|
249
|
+
{ pattern: /dolphin/, family: 'dolphin' },
|
|
250
|
+
{ pattern: /wizard/, family: 'wizard' },
|
|
251
|
+
{ pattern: /neural-chat/, family: 'neural-chat' },
|
|
252
|
+
{ pattern: /orca/, family: 'orca' },
|
|
253
|
+
{ pattern: /vicuna/, family: 'vicuna' },
|
|
254
|
+
{ pattern: /yi/, family: 'yi' },
|
|
255
|
+
{ pattern: /solar/, family: 'solar' },
|
|
256
|
+
{ pattern: /command-r/, family: 'command-r' },
|
|
257
|
+
{ pattern: /nomic-embed/, family: 'nomic-embed' },
|
|
258
|
+
{ pattern: /mxbai-embed/, family: 'mxbai-embed' },
|
|
259
|
+
{ pattern: /bge/, family: 'bge' },
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
for (const { pattern, family } of families) {
|
|
263
|
+
if (pattern.test(id)) {
|
|
264
|
+
return family;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return 'other';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Insert or update a variant
|
|
273
|
+
*/
|
|
274
|
+
upsertVariant(variant) {
|
|
275
|
+
const sql = `
|
|
276
|
+
INSERT INTO variants (model_id, tag, params_b, quant, size_gb, context_length, input_types, is_moe, expert_count)
|
|
277
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
278
|
+
ON CONFLICT(model_id, tag) DO UPDATE SET
|
|
279
|
+
params_b = excluded.params_b,
|
|
280
|
+
quant = excluded.quant,
|
|
281
|
+
size_gb = excluded.size_gb,
|
|
282
|
+
context_length = excluded.context_length,
|
|
283
|
+
input_types = excluded.input_types,
|
|
284
|
+
is_moe = excluded.is_moe,
|
|
285
|
+
expert_count = excluded.expert_count
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
this.run(sql, [
|
|
289
|
+
variant.model_id,
|
|
290
|
+
variant.tag,
|
|
291
|
+
variant.params_b || null,
|
|
292
|
+
variant.quant || null,
|
|
293
|
+
variant.size_gb || null,
|
|
294
|
+
variant.context_length || 4096,
|
|
295
|
+
JSON.stringify(variant.input_types || ['text']),
|
|
296
|
+
variant.is_moe ? 1 : 0,
|
|
297
|
+
variant.expert_count || null
|
|
298
|
+
]);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Add benchmark result
|
|
303
|
+
*/
|
|
304
|
+
addBenchmark(benchmark) {
|
|
305
|
+
const sql = `
|
|
306
|
+
INSERT INTO benchmarks (variant_id, hardware_fingerprint, tokens_per_second, time_to_first_token, memory_used_gb, backend)
|
|
307
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
308
|
+
`;
|
|
309
|
+
|
|
310
|
+
this.run(sql, [
|
|
311
|
+
benchmark.variant_id,
|
|
312
|
+
benchmark.hardware_fingerprint,
|
|
313
|
+
benchmark.tokens_per_second,
|
|
314
|
+
benchmark.time_to_first_token,
|
|
315
|
+
benchmark.memory_used_gb,
|
|
316
|
+
benchmark.backend
|
|
317
|
+
]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ==================== SEARCH OPERATIONS ====================
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Search models with filters
|
|
324
|
+
*/
|
|
325
|
+
searchModels(query = '', filters = {}) {
|
|
326
|
+
let sql = `
|
|
327
|
+
SELECT m.*,
|
|
328
|
+
COUNT(DISTINCT v.id) as variant_count,
|
|
329
|
+
MIN(v.size_gb) as min_size_gb,
|
|
330
|
+
MAX(v.size_gb) as max_size_gb,
|
|
331
|
+
MIN(v.params_b) as min_params_b,
|
|
332
|
+
MAX(v.params_b) as max_params_b
|
|
333
|
+
FROM models m
|
|
334
|
+
LEFT JOIN variants v ON m.id = v.model_id
|
|
335
|
+
WHERE 1=1
|
|
336
|
+
`;
|
|
337
|
+
const params = [];
|
|
338
|
+
|
|
339
|
+
// Text search
|
|
340
|
+
if (query) {
|
|
341
|
+
sql += ` AND (m.id LIKE ? OR m.name LIKE ? OR m.description LIKE ?)`;
|
|
342
|
+
const searchPattern = `%${query}%`;
|
|
343
|
+
params.push(searchPattern, searchPattern, searchPattern);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Family filter
|
|
347
|
+
if (filters.family) {
|
|
348
|
+
sql += ` AND m.family = ?`;
|
|
349
|
+
params.push(filters.family);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Type filter
|
|
353
|
+
if (filters.type) {
|
|
354
|
+
sql += ` AND m.type = ?`;
|
|
355
|
+
params.push(filters.type);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Capability filter
|
|
359
|
+
if (filters.capability) {
|
|
360
|
+
sql += ` AND m.capabilities LIKE ?`;
|
|
361
|
+
params.push(`%${filters.capability}%`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Min pulls filter
|
|
365
|
+
if (filters.minPulls) {
|
|
366
|
+
sql += ` AND m.pulls >= ?`;
|
|
367
|
+
params.push(filters.minPulls);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
sql += ` GROUP BY m.id`;
|
|
371
|
+
|
|
372
|
+
// Params range filter (post-group)
|
|
373
|
+
if (filters.minParams || filters.maxParams) {
|
|
374
|
+
sql += ` HAVING 1=1`;
|
|
375
|
+
if (filters.minParams) {
|
|
376
|
+
sql += ` AND max_params_b >= ?`;
|
|
377
|
+
params.push(filters.minParams);
|
|
378
|
+
}
|
|
379
|
+
if (filters.maxParams) {
|
|
380
|
+
sql += ` AND min_params_b <= ?`;
|
|
381
|
+
params.push(filters.maxParams);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Size range filter (post-group)
|
|
386
|
+
if (filters.maxSizeGB) {
|
|
387
|
+
if (!sql.includes('HAVING')) sql += ` HAVING 1=1`;
|
|
388
|
+
sql += ` AND min_size_gb <= ?`;
|
|
389
|
+
params.push(filters.maxSizeGB);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Order by
|
|
393
|
+
const orderBy = filters.orderBy || 'pulls';
|
|
394
|
+
const orderDir = filters.orderDir || 'DESC';
|
|
395
|
+
sql += ` ORDER BY m.${orderBy} ${orderDir}`;
|
|
396
|
+
|
|
397
|
+
// Limit
|
|
398
|
+
if (filters.limit) {
|
|
399
|
+
sql += ` LIMIT ?`;
|
|
400
|
+
params.push(filters.limit);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return this.all(sql, params);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get variants for a model
|
|
408
|
+
*/
|
|
409
|
+
getVariants(modelId, filters = {}) {
|
|
410
|
+
let sql = `
|
|
411
|
+
SELECT v.*, m.name as model_name, m.family, m.pulls
|
|
412
|
+
FROM variants v
|
|
413
|
+
JOIN models m ON v.model_id = m.id
|
|
414
|
+
WHERE v.model_id = ?
|
|
415
|
+
`;
|
|
416
|
+
const params = [modelId];
|
|
417
|
+
|
|
418
|
+
if (filters.quant) {
|
|
419
|
+
sql += ` AND v.quant = ?`;
|
|
420
|
+
params.push(filters.quant);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (filters.maxSizeGB) {
|
|
424
|
+
sql += ` AND v.size_gb <= ?`;
|
|
425
|
+
params.push(filters.maxSizeGB);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (filters.minParams) {
|
|
429
|
+
sql += ` AND v.params_b >= ?`;
|
|
430
|
+
params.push(filters.minParams);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (filters.maxParams) {
|
|
434
|
+
sql += ` AND v.params_b <= ?`;
|
|
435
|
+
params.push(filters.maxParams);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
sql += ` ORDER BY v.params_b DESC, v.size_gb DESC`;
|
|
439
|
+
|
|
440
|
+
return this.all(sql, params);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Get all variants matching hardware constraints
|
|
445
|
+
*/
|
|
446
|
+
getVariantsForHardware(maxSizeGB, filters = {}) {
|
|
447
|
+
let sql = `
|
|
448
|
+
SELECT v.*, m.name as model_name, m.family, m.pulls, m.capabilities, m.type
|
|
449
|
+
FROM variants v
|
|
450
|
+
JOIN models m ON v.model_id = m.id
|
|
451
|
+
WHERE v.size_gb <= ?
|
|
452
|
+
`;
|
|
453
|
+
const params = [maxSizeGB];
|
|
454
|
+
|
|
455
|
+
if (filters.category) {
|
|
456
|
+
sql += ` AND m.capabilities LIKE ?`;
|
|
457
|
+
params.push(`%${filters.category}%`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (filters.family) {
|
|
461
|
+
sql += ` AND m.family = ?`;
|
|
462
|
+
params.push(filters.family);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (filters.quant) {
|
|
466
|
+
sql += ` AND v.quant = ?`;
|
|
467
|
+
params.push(filters.quant);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (filters.minContext) {
|
|
471
|
+
sql += ` AND v.context_length >= ?`;
|
|
472
|
+
params.push(filters.minContext);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
sql += ` ORDER BY m.pulls DESC, v.params_b DESC`;
|
|
476
|
+
|
|
477
|
+
if (filters.limit) {
|
|
478
|
+
sql += ` LIMIT ?`;
|
|
479
|
+
params.push(filters.limit);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return this.all(sql, params);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Search variants by query (searches model names/descriptions)
|
|
487
|
+
*/
|
|
488
|
+
searchVariants(query = '', filters = {}) {
|
|
489
|
+
let sql = `
|
|
490
|
+
SELECT v.*, m.name as model_name, m.family, m.pulls, m.capabilities, m.type, m.description
|
|
491
|
+
FROM variants v
|
|
492
|
+
JOIN models m ON v.model_id = m.id
|
|
493
|
+
WHERE 1=1
|
|
494
|
+
`;
|
|
495
|
+
const params = [];
|
|
496
|
+
|
|
497
|
+
// Text search on model id, name, description
|
|
498
|
+
if (query) {
|
|
499
|
+
sql += ` AND (m.id LIKE ? OR m.name LIKE ? OR m.description LIKE ?)`;
|
|
500
|
+
const searchPattern = `%${query}%`;
|
|
501
|
+
params.push(searchPattern, searchPattern, searchPattern);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Size filters
|
|
505
|
+
if (filters.maxSize) {
|
|
506
|
+
sql += ` AND (v.size_gb IS NULL OR v.size_gb <= ?)`;
|
|
507
|
+
params.push(filters.maxSize);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (filters.minSize) {
|
|
511
|
+
sql += ` AND (v.size_gb IS NULL OR v.size_gb >= ?)`;
|
|
512
|
+
params.push(filters.minSize);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Quantization filter
|
|
516
|
+
if (filters.quant) {
|
|
517
|
+
sql += ` AND v.quant = ?`;
|
|
518
|
+
params.push(filters.quant.toUpperCase());
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Family filter
|
|
522
|
+
if (filters.family) {
|
|
523
|
+
sql += ` AND m.family = ?`;
|
|
524
|
+
params.push(filters.family.toLowerCase());
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Category/capability filter
|
|
528
|
+
if (filters.category) {
|
|
529
|
+
sql += ` AND m.capabilities LIKE ?`;
|
|
530
|
+
params.push(`%${filters.category}%`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
sql += ` ORDER BY m.pulls DESC, v.params_b DESC, v.size_gb DESC`;
|
|
534
|
+
|
|
535
|
+
if (filters.limit) {
|
|
536
|
+
sql += ` LIMIT ?`;
|
|
537
|
+
params.push(filters.limit);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return this.all(sql, params);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Get benchmarks for a variant on specific hardware
|
|
545
|
+
*/
|
|
546
|
+
getBenchmarks(variantId, hardwareFingerprint = null) {
|
|
547
|
+
let sql = `SELECT * FROM benchmarks WHERE variant_id = ?`;
|
|
548
|
+
const params = [variantId];
|
|
549
|
+
|
|
550
|
+
if (hardwareFingerprint) {
|
|
551
|
+
sql += ` AND hardware_fingerprint = ?`;
|
|
552
|
+
params.push(hardwareFingerprint);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
sql += ` ORDER BY created_at DESC`;
|
|
556
|
+
|
|
557
|
+
return this.all(sql, params);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ==================== SYNC OPERATIONS ====================
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Get last sync timestamp
|
|
564
|
+
*/
|
|
565
|
+
getLastSync() {
|
|
566
|
+
const result = this.get(`SELECT value FROM sync_meta WHERE key = 'last_sync'`);
|
|
567
|
+
return result ? result.value : null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Set last sync timestamp
|
|
572
|
+
*/
|
|
573
|
+
setLastSync(timestamp) {
|
|
574
|
+
this.run(`
|
|
575
|
+
INSERT INTO sync_meta (key, value, updated_at)
|
|
576
|
+
VALUES ('last_sync', ?, CURRENT_TIMESTAMP)
|
|
577
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
578
|
+
value = excluded.value,
|
|
579
|
+
updated_at = CURRENT_TIMESTAMP
|
|
580
|
+
`, [timestamp]);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Get total model count
|
|
585
|
+
*/
|
|
586
|
+
getModelCount() {
|
|
587
|
+
const result = this.get(`SELECT COUNT(*) as count FROM models`);
|
|
588
|
+
return result ? result.count : 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Get total variant count
|
|
593
|
+
*/
|
|
594
|
+
getVariantCount() {
|
|
595
|
+
const result = this.get(`SELECT COUNT(*) as count FROM variants`);
|
|
596
|
+
return result ? result.count : 0;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Get database stats
|
|
601
|
+
*/
|
|
602
|
+
getStats() {
|
|
603
|
+
return {
|
|
604
|
+
models: this.getModelCount(),
|
|
605
|
+
variants: this.getVariantCount(),
|
|
606
|
+
lastSync: this.getLastSync(),
|
|
607
|
+
families: this.all(`SELECT family, COUNT(*) as count FROM models GROUP BY family ORDER BY count DESC`),
|
|
608
|
+
topModels: this.all(`SELECT id, name, pulls FROM models ORDER BY pulls DESC LIMIT 10`)
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Clear all data
|
|
614
|
+
*/
|
|
615
|
+
clear() {
|
|
616
|
+
this.run(`DELETE FROM benchmarks`);
|
|
617
|
+
this.run(`DELETE FROM variants`);
|
|
618
|
+
this.run(`DELETE FROM models`);
|
|
619
|
+
this.run(`DELETE FROM sync_meta`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Close database connection
|
|
624
|
+
*/
|
|
625
|
+
close() {
|
|
626
|
+
if (this.db) {
|
|
627
|
+
if (this.useBetterSqlite) {
|
|
628
|
+
this.db.close();
|
|
629
|
+
} else {
|
|
630
|
+
this.saveToFile();
|
|
631
|
+
this.db.close();
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
module.exports = ModelDatabase;
|