offgrid-ai 0.6.2 → 0.6.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/package.json +1 -1
- package/src/benchmark.mjs +28 -10
- package/src/cli.mjs +67 -16
package/package.json
CHANGED
package/src/benchmark.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
import { ensureDirs, loadConfig, saveConfig } from "./config.mjs";
|
|
9
|
+
import { backendFor } from "./backends.mjs";
|
|
9
10
|
import { pc, createPrompt, renderRows, renderSection } from "./ui.mjs";
|
|
10
11
|
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
@@ -137,11 +138,35 @@ export async function linkBenchmarkRepo(prompt) {
|
|
|
137
138
|
|
|
138
139
|
// ── Create a benchmark run directory ──────────────────────────────────────
|
|
139
140
|
|
|
141
|
+
function harnessDisplayName(id) {
|
|
142
|
+
if (id === "pi") return "Pi";
|
|
143
|
+
return String(id).replace(/[-_]+/gu, " ").replace(/\b\w/gu, (char) => char.toUpperCase());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function intendedRunnerForProfile(profile) {
|
|
147
|
+
if (!profile) return "your tool";
|
|
148
|
+
const harnessEntries = Object.entries(profile.harnesses ?? {}).filter(([, config]) => config?.enabled !== false);
|
|
149
|
+
const [id] = harnessEntries.find(([key]) => key === "pi") ?? harnessEntries[0] ?? ["pi"];
|
|
150
|
+
return harnessDisplayName(id);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function printBenchmarkNextSteps({ repoPath, runDirectory, profile, modelId, runnerLabel }) {
|
|
154
|
+
const runCommand = profile ? `offgrid-ai run ${profile.id}` : null;
|
|
155
|
+
const runnerCommand = runCommand ?? `Open ${runnerLabel} for ${modelId}`;
|
|
156
|
+
|
|
157
|
+
console.log("");
|
|
158
|
+
console.log(pc.bold("Next steps"));
|
|
159
|
+
console.log(` 1. Open the gallery. If it is not running: ${pc.cyan(`cd ${repoPath} && npm run dev`)}`);
|
|
160
|
+
console.log(` 2. ${pc.cyan(`cd ${runDirectory}`)}`);
|
|
161
|
+
console.log(` 3. ${pc.cyan(runnerCommand)}, then copy this run's prompt from the gallery and paste it into ${runnerLabel}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
140
164
|
async function prepareBenchmarkRun({ repoPath, benchmark, kind, modelId, modelSource, backendLabel, profile }) {
|
|
141
165
|
const toolPrompt = buildToolPrompt(benchmark, kind);
|
|
142
166
|
const now = new Date();
|
|
143
167
|
const runId = createRunId(now);
|
|
144
168
|
const modelSlug = slugModelId(modelId);
|
|
169
|
+
const runnerLabel = intendedRunnerForProfile(profile);
|
|
145
170
|
const runsDir = join(repoPath, "runs");
|
|
146
171
|
const benchmarkDirectory = join(runsDir, benchmark.id);
|
|
147
172
|
const modelDirectory = join(benchmarkDirectory, modelSlug);
|
|
@@ -166,8 +191,8 @@ async function prepareBenchmarkRun({ repoPath, benchmark, kind, modelId, modelSo
|
|
|
166
191
|
: { metadata: "metadata.json", prompt: "prompt.md", html: "index.html", preview: "preview.png", video: "preview.webm", rawResponse: "response.raw.txt" },
|
|
167
192
|
runner: {
|
|
168
193
|
mode: modelSource === "cloud" ? "manual" : "external",
|
|
169
|
-
intendedRunner: profile ?
|
|
170
|
-
...(profile ? { tool: "pi" } : {}),
|
|
194
|
+
intendedRunner: profile ? runnerLabel : undefined,
|
|
195
|
+
...(profile?.harnesses?.pi || runnerLabel === "Pi" ? { tool: "pi" } : {}),
|
|
171
196
|
...(modelSource ? { modelSource } : {}),
|
|
172
197
|
...(backendLabel ? { backendLabel } : {}),
|
|
173
198
|
...(profile?.baseUrl ? { baseUrl: profile.baseUrl } : {}),
|
|
@@ -190,12 +215,7 @@ async function prepareBenchmarkRun({ repoPath, benchmark, kind, modelId, modelSo
|
|
|
190
215
|
["Source", backendLabel || modelSource],
|
|
191
216
|
])));
|
|
192
217
|
|
|
193
|
-
|
|
194
|
-
console.log(pc.bold("Next steps"));
|
|
195
|
-
console.log(` 1. ${pc.cyan(`offgrid-ai run ${profile ? profile.id : "<profile>"}`)}`);
|
|
196
|
-
console.log(` 2. ${pc.cyan(`cd ${repoPath} && npm run dev`)}`);
|
|
197
|
-
console.log(` 3. In the gallery, find your run and copy the prompt from the run details`);
|
|
198
|
-
console.log(" 4. In Pi, paste the prompt");
|
|
218
|
+
printBenchmarkNextSteps({ repoPath, runDirectory, profile, modelId, runnerLabel });
|
|
199
219
|
|
|
200
220
|
return runDirectory;
|
|
201
221
|
}
|
|
@@ -226,7 +246,6 @@ export async function benchmarkForProfile(profile) {
|
|
|
226
246
|
const selectedBenchmark = benchmarks.find((b) => b.id === benchmarkId);
|
|
227
247
|
if (!selectedBenchmark) return;
|
|
228
248
|
|
|
229
|
-
const { backendFor } = await import("./backends.mjs");
|
|
230
249
|
const modelId = profile.modelAlias;
|
|
231
250
|
const modelSource = profile.providerId === "llama-cpp-mtp" ? "llama-cpp-mtp" : profile.backend === "ollama" ? "ollama" : profile.backend === "omlx" ? "omlx" : "llama-cpp";
|
|
232
251
|
const backendLabel = backendFor(profile.backend).label;
|
|
@@ -265,7 +284,6 @@ export async function benchmarkFlow() {
|
|
|
265
284
|
if (!selectedBenchmark) return;
|
|
266
285
|
|
|
267
286
|
const { loadProfiles } = await import("./profiles.mjs");
|
|
268
|
-
const { backendFor } = await import("./backends.mjs");
|
|
269
287
|
|
|
270
288
|
const profiles = await loadProfiles();
|
|
271
289
|
const source = await prompt.choice("Model source", [
|
package/src/cli.mjs
CHANGED
|
@@ -272,37 +272,88 @@ function buildCatalogItems(normalized) {
|
|
|
272
272
|
];
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
const OPTION_SEPARATOR = pc.dim(" │ ");
|
|
276
|
+
const OPTION_STATUS_WIDTH = 12;
|
|
277
|
+
const OPTION_SOURCE_WIDTH = 14;
|
|
278
|
+
|
|
279
|
+
function optionTag(text, color, width) {
|
|
280
|
+
const padded = String(text).padEnd(width);
|
|
281
|
+
return color ? color(padded) : padded;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function optionStatusTag(kind) {
|
|
285
|
+
const statuses = {
|
|
286
|
+
running: ["RUNNING", pc.green],
|
|
287
|
+
ready: ["SET UP", pc.green],
|
|
288
|
+
missing: ["FILE MISSING", pc.red],
|
|
289
|
+
setup: ["NEEDS SETUP", pc.yellow],
|
|
290
|
+
};
|
|
291
|
+
const [text, color] = statuses[kind] ?? [kind, pc.dim];
|
|
292
|
+
return optionTag(text, color, OPTION_STATUS_WIDTH);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function optionSourceTag(sourceId, label) {
|
|
296
|
+
const colors = {
|
|
297
|
+
"llama-cpp": pc.cyan,
|
|
298
|
+
"llama-cpp-mtp": pc.blue,
|
|
299
|
+
ollama: pc.green,
|
|
300
|
+
omlx: pc.magenta,
|
|
301
|
+
gguf: pc.cyan,
|
|
302
|
+
};
|
|
303
|
+
return optionTag(label, colors[sourceId] ?? pc.dim, OPTION_SOURCE_WIDTH);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function optionLabel({ status, source, name, details = [] }) {
|
|
307
|
+
return [status, source, pc.bold(name), ...details].filter(Boolean).join(OPTION_SEPARATOR);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function optionHint(parts) {
|
|
311
|
+
return parts.filter(Boolean).join(" · ");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function modelSelectOption(item, { runningProfilesNow, drafters }) {
|
|
276
315
|
if (item.type === "profile") {
|
|
277
316
|
const { profile } = item;
|
|
317
|
+
const backend = backendFor(profile.backend);
|
|
278
318
|
const running = runningProfilesNow.some((r) => r.id === profile.id);
|
|
279
319
|
const fileMissing = item.fileMissing;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
else status = pc.dim("● ready");
|
|
284
|
-
// MTP label: enabled vs available vs not available
|
|
320
|
+
const status = optionStatusTag(fileMissing ? "missing" : running ? "running" : "ready");
|
|
321
|
+
const source = optionSourceTag(profile.backend, backend.label);
|
|
322
|
+
const caps = profile.capabilities ?? {};
|
|
285
323
|
let mtpLabel;
|
|
286
|
-
if (profile.drafterPath) mtpLabel = pc.green("MTP");
|
|
287
|
-
else if ((profile.
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
324
|
+
if (profile.drafterPath) mtpLabel = pc.green("MTP on");
|
|
325
|
+
else if (drafters ? matchDrafter(profile.modelPath, drafters) : null) mtpLabel = pc.yellow("MTP available");
|
|
326
|
+
else if (caps.architecture === "gemma4") mtpLabel = pc.yellow("MTP needs drafter");
|
|
327
|
+
const ctxLabel = profile.flags?.ctxSize ? pc.dim(`${(profile.flags.ctxSize / 1000).toFixed(0)}k ctx`) : null;
|
|
328
|
+
const label = optionLabel({ status, source, name: profile.label, details: [ctxLabel, mtpLabel] });
|
|
329
|
+
return { value: itemKey(item), label, hint: optionHint([profile.modelAlias, humanCapabilitySummary(caps), profile.baseUrl]) };
|
|
291
330
|
}
|
|
292
331
|
if (item.type === "new") {
|
|
293
332
|
const { model, drafter } = item;
|
|
294
333
|
const caps = detectCapabilities(model.path, model.mmprojPath);
|
|
295
334
|
const mtpAvailable = caps.mtp || Boolean(drafter);
|
|
296
335
|
let mtpLabel;
|
|
297
|
-
if (mtpAvailable) mtpLabel = pc.green("MTP
|
|
298
|
-
else if (caps.architecture === "gemma4") mtpLabel = pc.yellow("MTP");
|
|
299
|
-
const label =
|
|
300
|
-
|
|
336
|
+
if (mtpAvailable) mtpLabel = pc.green("MTP available");
|
|
337
|
+
else if (caps.architecture === "gemma4") mtpLabel = pc.yellow("MTP needs drafter");
|
|
338
|
+
const label = optionLabel({
|
|
339
|
+
status: optionStatusTag("setup"),
|
|
340
|
+
source: optionSourceTag("gguf", "GGUF file"),
|
|
341
|
+
name: model.label,
|
|
342
|
+
details: [pc.dim(formatBytes(model.sizeBytes)), mtpLabel],
|
|
343
|
+
});
|
|
344
|
+
return { value: itemKey(item), label, hint: optionHint([basename(model.path), humanCapabilitySummary(caps), model.quant]) };
|
|
301
345
|
}
|
|
302
346
|
// managed
|
|
303
347
|
const { model, backendId } = item;
|
|
304
348
|
const backend = BACKENDS[backendId];
|
|
305
|
-
|
|
349
|
+
const details = [model.quant ? pc.dim(model.quant) : null, model.sizeBytes ? pc.dim(formatBytes(model.sizeBytes)) : null];
|
|
350
|
+
const label = optionLabel({
|
|
351
|
+
status: optionStatusTag("setup"),
|
|
352
|
+
source: optionSourceTag(backendId, backend.label),
|
|
353
|
+
name: model.label,
|
|
354
|
+
details,
|
|
355
|
+
});
|
|
356
|
+
return { value: itemKey(item), label, hint: optionHint([model.id, "available through local backend"]) };
|
|
306
357
|
}
|
|
307
358
|
|
|
308
359
|
function actionsForItem(item) {
|