offgrid-ai 0.3.20 → 0.3.22
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/package.json +1 -1
- package/src/backends.mjs +19 -10
- package/src/cli.mjs +5 -5
- package/src/profile-setup.mjs +55 -28
- package/src/ui.mjs +2 -2
package/package.json
CHANGED
package/src/backends.mjs
CHANGED
|
@@ -72,16 +72,18 @@ async function scanOllamaModels() {
|
|
|
72
72
|
if (!response.ok) return [];
|
|
73
73
|
const body = await response.json();
|
|
74
74
|
if (!Array.isArray(body?.models)) return [];
|
|
75
|
-
return body.models
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
return body.models
|
|
76
|
+
.filter((model) => isLocalOllamaModel(model))
|
|
77
|
+
.map((model) => ({
|
|
78
|
+
id: model.name,
|
|
79
|
+
label: ollamaLabel(model.name),
|
|
80
|
+
aliasSuggestion: model.name,
|
|
81
|
+
sizeBytes: model.size ?? 0,
|
|
82
|
+
quant: model.details?.quantization_level,
|
|
83
|
+
family: model.details?.family,
|
|
84
|
+
backend: "ollama",
|
|
85
|
+
source: "ollama",
|
|
86
|
+
})).sort((a, b) => a.label.localeCompare(b.label));
|
|
85
87
|
} catch {
|
|
86
88
|
return [];
|
|
87
89
|
}
|
|
@@ -112,6 +114,13 @@ async function scanOmlxModels() {
|
|
|
112
114
|
|
|
113
115
|
// ── Labels ──────────────────────────────────────────────────────────────
|
|
114
116
|
|
|
117
|
+
function isLocalOllamaModel(model) {
|
|
118
|
+
const name = String(model?.name ?? "");
|
|
119
|
+
if (/:cloud(?:$|\b)/i.test(name)) return false;
|
|
120
|
+
if (!Number.isFinite(model?.size) || model.size <= 0) return false;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
115
124
|
function ollamaLabel(name) {
|
|
116
125
|
return name.replace(/[-_]/g, " ").replace(/^gemma\b/i, "Gemma").replace(/^qwen/i, "Qwen");
|
|
117
126
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -280,7 +280,7 @@ async function printModelCatalog({ profiles, newModels, managedItems }, items =
|
|
|
280
280
|
const backendItems = managedItems.filter((item) => item.backendId === backendId);
|
|
281
281
|
if (backendItems.length === 0) continue;
|
|
282
282
|
const be = BACKENDS[backendId];
|
|
283
|
-
console.log("\n" + pc.bold(`
|
|
283
|
+
console.log("\n" + pc.bold(`Local models via ${be.label}`));
|
|
284
284
|
for (const { model } of backendItems.slice(0, 10)) {
|
|
285
285
|
const num = itemNumber((item) => item.type === "managed" && item.backendId === backendId && item.model.id === model.id);
|
|
286
286
|
console.log(managedModelCard(num, model, be));
|
|
@@ -307,13 +307,13 @@ function downloadedModelCard(num, model, caps) {
|
|
|
307
307
|
["Good for", humanCapabilitySummary(caps)],
|
|
308
308
|
["Size", formatBytes(model.sizeBytes)],
|
|
309
309
|
["When selected", "offgrid-ai will recommend safe local settings"],
|
|
310
|
-
]), { formatBorder:
|
|
310
|
+
]), { formatBorder: pc.yellow });
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
function managedModelCard(num, model, backend) {
|
|
314
314
|
return renderCard(`${num}. ${model.label}`, renderRows([
|
|
315
|
-
["Status", statusText("info",
|
|
316
|
-
["
|
|
315
|
+
["Status", statusText("info", `Local model via ${backend.label}`)],
|
|
316
|
+
["Runs with", backend.label],
|
|
317
317
|
["Model ID", pc.cyan(model.id)],
|
|
318
318
|
...(model.quant ? [["Size/type", model.quant]] : []),
|
|
319
319
|
]), { formatBorder: pc.magenta });
|
|
@@ -438,7 +438,7 @@ function printGgufModelDetails(model) {
|
|
|
438
438
|
function printManagedModelDetails(model, backend) {
|
|
439
439
|
console.log("\n" + renderSection(`${backend.label} model`, renderRows([
|
|
440
440
|
["Name", pc.bold(model.label)],
|
|
441
|
-
["Status", pc.green(
|
|
441
|
+
["Status", pc.green(`Local model via ${backend.label}`)],
|
|
442
442
|
["Model ID", pc.cyan(model.id)],
|
|
443
443
|
["Quant", model.quant ?? "unknown"],
|
|
444
444
|
["Family", model.family ?? "unknown"],
|
package/src/profile-setup.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { estimateMemory } from "./estimate.mjs";
|
|
2
|
-
import { pc, formatBytes, renderRows, renderSection
|
|
2
|
+
import { pc, formatBytes, renderRows, renderSection } from "./ui.mjs";
|
|
3
3
|
|
|
4
4
|
const CACHE_CHOICES = [
|
|
5
|
-
{ value: "bf16", label: "
|
|
6
|
-
{ value: "f16", label: "
|
|
7
|
-
{ value: "q8_0", label: "
|
|
8
|
-
{ value: "q4_0", label: "
|
|
5
|
+
{ value: "bf16", label: "bf16", hint: "default: stable, good quality" },
|
|
6
|
+
{ value: "f16", label: "f16", hint: "stable fallback, similar memory to bf16" },
|
|
7
|
+
{ value: "q8_0", label: "q8_0", hint: "lower memory, usually safe" },
|
|
8
|
+
{ value: "q4_0", label: "q4_0", hint: "lowest memory, quality/speed tradeoff" },
|
|
9
9
|
];
|
|
10
10
|
|
|
11
11
|
const GENERAL_DEFAULTS = {
|
|
@@ -26,49 +26,64 @@ export async function configureLocalProfile(prompt, profile) {
|
|
|
26
26
|
const caps = profile.capabilities ?? {};
|
|
27
27
|
|
|
28
28
|
console.log("");
|
|
29
|
-
console.log(renderSection("
|
|
29
|
+
console.log(renderSection("Model setup", renderRows([
|
|
30
30
|
["Model", pc.bold(profile.label)],
|
|
31
|
-
["
|
|
32
|
-
["
|
|
33
|
-
["
|
|
34
|
-
["
|
|
31
|
+
["Detected", detectionSummary(caps)],
|
|
32
|
+
["Context", `${profile.flags.ctxSize.toLocaleString()} tokens`],
|
|
33
|
+
["KV cache", `${profile.flags.cacheTypeK}/${profile.flags.cacheTypeV}`],
|
|
34
|
+
["Sampling", samplingSummary(profile.flags)],
|
|
35
35
|
])));
|
|
36
|
-
console.log(pc.dim("
|
|
36
|
+
console.log(pc.dim("Larger context windows use more memory. KV cache precision controls memory used by attention history."));
|
|
37
|
+
console.log(pc.dim("Sampling defaults are shown for transparency; you can edit command.json later if needed.\n"));
|
|
37
38
|
|
|
38
39
|
if (caps.mtp) {
|
|
39
|
-
console.log(renderSection("
|
|
40
|
-
|
|
40
|
+
console.log(renderSection("MTP detected", renderRows([
|
|
41
|
+
["Backend", "llama.cpp MTP"],
|
|
42
|
+
["Port", "8081"],
|
|
43
|
+
["Flags", "--spec-type draft-mtp --spec-draft-n-max 2"],
|
|
44
|
+
])));
|
|
45
|
+
const useMtp = await prompt.yesNo("Use MTP speculative decoding flags?", true);
|
|
41
46
|
configured = useMtp ? applyMtpDefaults(configured) : removeMtpDefaults(configured);
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
if (caps.qat) {
|
|
45
50
|
console.log("");
|
|
46
|
-
console.log(renderSection("
|
|
51
|
+
console.log(renderSection("QAT detected", renderRows([
|
|
52
|
+
["Meaning", "quantization-aware trained"],
|
|
53
|
+
["Runtime flags", "none required"],
|
|
54
|
+
])));
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
if (caps.thinking) {
|
|
50
58
|
console.log("");
|
|
51
|
-
console.log(renderSection("
|
|
52
|
-
|
|
59
|
+
console.log(renderSection("Thinking model detected", renderRows([
|
|
60
|
+
["Defaults", "thinking / loop-safe"],
|
|
61
|
+
["Flags", "--top-k 64 --presence-penalty 0 --repeat-penalty 1.1"],
|
|
62
|
+
["Template", "--chat-template-kwargs { enable_thinking: true }"],
|
|
63
|
+
])));
|
|
64
|
+
const useThinking = await prompt.yesNo("Use these thinking/loop-safe defaults?", true);
|
|
53
65
|
configured = useThinking ? applyThinkingDefaults(configured) : removeThinkingDefaults(configured);
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
const ctxSize = await prompt.number("
|
|
57
|
-
const cacheTypeK = await prompt.choice("
|
|
58
|
-
const cacheTypeV = await prompt.choice("
|
|
68
|
+
const ctxSize = await prompt.number("Context window tokens", configured.flags.ctxSize, 1024, 1048576);
|
|
69
|
+
const cacheTypeK = await prompt.choice("K cache precision", CACHE_CHOICES, configured.flags.cacheTypeK);
|
|
70
|
+
const cacheTypeV = await prompt.choice("V cache precision", CACHE_CHOICES, configured.flags.cacheTypeV);
|
|
59
71
|
configured = applyRuntimeFlagOverrides(configured, { ctxSize, cacheTypeK, cacheTypeV });
|
|
60
72
|
|
|
61
73
|
console.log("");
|
|
62
|
-
console.log(renderSection("
|
|
63
|
-
["
|
|
64
|
-
["
|
|
65
|
-
["
|
|
66
|
-
["
|
|
67
|
-
["
|
|
74
|
+
console.log(renderSection("Defaults", renderRows([
|
|
75
|
+
["Backend", configured.backend],
|
|
76
|
+
["Endpoint", configured.baseUrl],
|
|
77
|
+
["Temperature", configured.flags.temperature],
|
|
78
|
+
["Top-p", configured.flags.topP],
|
|
79
|
+
["Top-k", configured.flags.topK],
|
|
80
|
+
["Min-p", configured.flags.minP],
|
|
81
|
+
["Presence penalty", configured.flags.presencePenalty],
|
|
82
|
+
["Repeat penalty", configured.flags.repeatPenalty],
|
|
68
83
|
])));
|
|
69
84
|
|
|
70
85
|
console.log("\n" + renderMemoryEstimate(configured));
|
|
71
|
-
if (!(await prompt.yesNo("Save
|
|
86
|
+
if (!(await prompt.yesNo("Save profile with these settings?", true))) return null;
|
|
72
87
|
return configured;
|
|
73
88
|
}
|
|
74
89
|
|
|
@@ -155,8 +170,8 @@ function renderMemoryEstimate(profile) {
|
|
|
155
170
|
const est = estimateMemory(profile.modelPath, profile.mmprojPath, null, profile.flags);
|
|
156
171
|
return renderSection("Memory estimate", renderRows([
|
|
157
172
|
["Estimated total", pc.bold(`~${formatBytes(est.totalBytes)}`)],
|
|
158
|
-
["Model
|
|
159
|
-
["
|
|
173
|
+
["Model", formatBytes(est.modelBytes)],
|
|
174
|
+
["KV cache", est.kvBytes ? `~${formatBytes(est.kvBytes)} (${profile.flags.ctxSize.toLocaleString()} ctx, ${profile.flags.cacheTypeK}/${profile.flags.cacheTypeV})` : "unknown"],
|
|
160
175
|
...(est.note ? [["Note", pc.yellow(est.note)]] : []),
|
|
161
176
|
]));
|
|
162
177
|
} catch {
|
|
@@ -164,6 +179,18 @@ function renderMemoryEstimate(profile) {
|
|
|
164
179
|
}
|
|
165
180
|
}
|
|
166
181
|
|
|
182
|
+
function detectionSummary(caps) {
|
|
183
|
+
const parts = [];
|
|
184
|
+
if (caps.architecture) parts.push(caps.architecture);
|
|
185
|
+
if (caps.quant) parts.push(caps.quant);
|
|
186
|
+
if (caps.mtp) parts.push("MTP");
|
|
187
|
+
if (caps.qat) parts.push("QAT");
|
|
188
|
+
|
|
189
|
+
if (caps.thinking) parts.push("thinking");
|
|
190
|
+
if (caps.vision) parts.push("vision");
|
|
191
|
+
return parts.length > 0 ? parts.join(" · ") : "standard GGUF";
|
|
192
|
+
}
|
|
193
|
+
|
|
167
194
|
function samplingSummary(flags) {
|
|
168
195
|
return `temp ${flags.temperature}, top-p ${flags.topP}, top-k ${flags.topK}`;
|
|
169
196
|
}
|
package/src/ui.mjs
CHANGED
|
@@ -83,8 +83,8 @@ export function humanCapabilitySummary(caps = {}) {
|
|
|
83
83
|
const parts = [];
|
|
84
84
|
if (caps.thinking) parts.push(pc.magenta("Reasoning"));
|
|
85
85
|
if (caps.vision) parts.push(pc.cyan("Vision"));
|
|
86
|
-
if (caps.mtp) parts.push(pc.blue("
|
|
87
|
-
if (caps.qat) parts.push(pc.green("
|
|
86
|
+
if (caps.mtp) parts.push(pc.blue("MTP"));
|
|
87
|
+
if (caps.qat) parts.push(pc.green("QAT"));
|
|
88
88
|
return parts.length > 0 ? parts.join(" · ") : "General chat";
|
|
89
89
|
}
|
|
90
90
|
|