offgrid-ai 0.15.3 → 0.15.5
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 +6 -4
- package/src/ui.mjs +42 -5
package/package.json
CHANGED
package/src/backends.mjs
CHANGED
|
@@ -102,16 +102,18 @@ async function scanOmlxModels() {
|
|
|
102
102
|
// The oMLX API can return the same model multiple times with different
|
|
103
103
|
// ID formats (e.g. "Qwen3.6-35B-A3B-OptiQ-4bit" and
|
|
104
104
|
// "mlx-community--Qwen3.6-35B-A3B-OptiQ-4bit"). Deduplicate by the
|
|
105
|
-
// normalized full name (publisher/model), keeping
|
|
106
|
-
// (which has the most complete metadata
|
|
105
|
+
// normalized full name (publisher/model with / separator), keeping
|
|
106
|
+
// the first entry (which has the most complete metadata).
|
|
107
107
|
const seen = new Set();
|
|
108
108
|
const deduped = [];
|
|
109
109
|
for (const model of body.data.filter(isChatOmlxModel)) {
|
|
110
110
|
const info = lookupOmlxModelInfo(model.id, infoMap);
|
|
111
111
|
const hasPublisher = model.id.includes("/") || model.id.includes("--");
|
|
112
112
|
const fullName = (!hasPublisher && info?.publisher) ? `${info.publisher}/${model.id}` : model.id;
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// Normalize: convert -- separator to / for dedup comparison
|
|
114
|
+
const normalized = fullName.replace(/--/g, "/");
|
|
115
|
+
if (seen.has(normalized)) continue;
|
|
116
|
+
seen.add(normalized);
|
|
115
117
|
deduped.push(model);
|
|
116
118
|
}
|
|
117
119
|
|
package/src/ui.mjs
CHANGED
|
@@ -159,6 +159,43 @@ export function statusText(kind, text) {
|
|
|
159
159
|
return color(text);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// ── Escape-to-cancel helper ─────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
function withEscape() {
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
let escapeTimer = null;
|
|
167
|
+
const onData = (data) => {
|
|
168
|
+
// Standalone Escape = 0x1b byte alone (arrow keys send 0x1b + more bytes)
|
|
169
|
+
if (data.length === 1 && data[0] === 0x1b) {
|
|
170
|
+
escapeTimer = setTimeout(() => controller.abort(), 50);
|
|
171
|
+
} else if (escapeTimer) {
|
|
172
|
+
clearTimeout(escapeTimer);
|
|
173
|
+
escapeTimer = null;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
process.stdin.on("data", onData);
|
|
177
|
+
const cleanup = () => {
|
|
178
|
+
process.stdin.removeListener("data", onData);
|
|
179
|
+
if (escapeTimer) clearTimeout(escapeTimer);
|
|
180
|
+
};
|
|
181
|
+
return { signal: controller.signal, cleanup };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function runPrompt(fn, config) {
|
|
185
|
+
const { signal, cleanup } = withEscape();
|
|
186
|
+
try {
|
|
187
|
+
return await fn(config, { signal });
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (err.name === "AbortPromptError") {
|
|
190
|
+
console.log(pc.dim("\nCancelled."));
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
throw err;
|
|
194
|
+
} finally {
|
|
195
|
+
cleanup();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
162
199
|
// ── Interactive prompt factory ──────────────────────────────────────────────
|
|
163
200
|
|
|
164
201
|
export function startInteractive(title = "offgrid-ai") {
|
|
@@ -169,7 +206,7 @@ export function startInteractive(title = "offgrid-ai") {
|
|
|
169
206
|
export function createPrompt() {
|
|
170
207
|
return {
|
|
171
208
|
async text(label, defaultValue) {
|
|
172
|
-
const value = await input
|
|
209
|
+
const value = await runPrompt(input, {
|
|
173
210
|
message: label,
|
|
174
211
|
default: defaultValue === undefined ? undefined : String(defaultValue),
|
|
175
212
|
});
|
|
@@ -177,7 +214,7 @@ export function createPrompt() {
|
|
|
177
214
|
},
|
|
178
215
|
|
|
179
216
|
async number(label, defaultValue, min, max) {
|
|
180
|
-
const value = await number
|
|
217
|
+
const value = await runPrompt(number, {
|
|
181
218
|
message: label,
|
|
182
219
|
default: defaultValue,
|
|
183
220
|
validate(input) {
|
|
@@ -190,7 +227,7 @@ export function createPrompt() {
|
|
|
190
227
|
},
|
|
191
228
|
|
|
192
229
|
async yesNo(label, defaultValue) {
|
|
193
|
-
return await confirm
|
|
230
|
+
return await runPrompt(confirm, { message: label, default: defaultValue });
|
|
194
231
|
},
|
|
195
232
|
|
|
196
233
|
async choice(label, choices, defaultValue) {
|
|
@@ -203,7 +240,7 @@ export function createPrompt() {
|
|
|
203
240
|
disabled: c.disabled || undefined,
|
|
204
241
|
};
|
|
205
242
|
});
|
|
206
|
-
return await inquirerSelect
|
|
243
|
+
return await runPrompt(inquirerSelect, {
|
|
207
244
|
message: label,
|
|
208
245
|
default: defaultValue,
|
|
209
246
|
choices: mapped,
|
|
@@ -241,7 +278,7 @@ export async function modelSelect(label, groups, { defaultKey, pageSize = 20 } =
|
|
|
241
278
|
});
|
|
242
279
|
}
|
|
243
280
|
}
|
|
244
|
-
return await inquirerSelect
|
|
281
|
+
return await runPrompt(inquirerSelect, {
|
|
245
282
|
message: label,
|
|
246
283
|
default: defaultKey,
|
|
247
284
|
choices,
|