Caching slash-loaded models to use.
Browse files- package.json +1 -1
- src/app/model-list.js +29 -3
- src/worker/boot-worker.js +3 -1
package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "localm",
|
| 3 |
-
"version": "1.1.
|
| 4 |
"description": "Chat application",
|
| 5 |
"scripts": {
|
| 6 |
"build": "esbuild src/index.js --target=es6 --bundle --sourcemap --outfile=./index.js --format=iife --external:fs --external:path --external:child_process --external:ws --external:katex/dist/katex.min.css",
|
|
|
|
| 1 |
{
|
| 2 |
"name": "localm",
|
| 3 |
+
"version": "1.1.20",
|
| 4 |
"description": "Chat application",
|
| 5 |
"scripts": {
|
| 6 |
"build": "esbuild src/index.js --target=es6 --bundle --sourcemap --outfile=./index.js --format=iife --external:fs --external:path --external:child_process --external:ws --external:katex/dist/katex.min.css",
|
src/app/model-list.js
CHANGED
|
@@ -92,15 +92,16 @@ export async function fetchBrowserModels() {
|
|
| 92 |
hasTokenizer: !!hasTokenizer,
|
| 93 |
missingFiles: !!missingFiles,
|
| 94 |
missingReason: missingReason || '',
|
| 95 |
-
|
|
|
|
| 96 |
});
|
| 97 |
} catch (e) {
|
| 98 |
return null;
|
| 99 |
}
|
| 100 |
}).filter(m => m !== null);
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
|
| 105 |
// Sort by downloads desc
|
| 106 |
withFiles.sort((a, b) => ((b && b.downloads) || 0) - ((a && a.downloads) || 0));
|
|
@@ -343,6 +344,31 @@ function detectRequiredFiles(model) {
|
|
| 343 |
return { hasOnnx, hasTokenizer, missingFiles: missing, missingReason: reason };
|
| 344 |
}
|
| 345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
/**
|
| 347 |
* Get fallback models if API fetch fails
|
| 348 |
* @returns {ModelInfo[]}
|
|
|
|
| 92 |
hasTokenizer: !!hasTokenizer,
|
| 93 |
missingFiles: !!missingFiles,
|
| 94 |
missingReason: missingReason || '',
|
| 95 |
+
downloads: m.downloads || 0,
|
| 96 |
+
tags: Array.isArray(m.tags) ? m.tags.slice() : []
|
| 97 |
});
|
| 98 |
} catch (e) {
|
| 99 |
return null;
|
| 100 |
}
|
| 101 |
}).filter(m => m !== null);
|
| 102 |
|
| 103 |
+
// Keep only models that have both ONNX and tokenizer files AND support chat
|
| 104 |
+
const withFiles = processed.filter(p => p && p.hasOnnx && p.hasTokenizer && isModelChatCapable(p));
|
| 105 |
|
| 106 |
// Sort by downloads desc
|
| 107 |
withFiles.sort((a, b) => ((b && b.downloads) || 0) - ((a && a.downloads) || 0));
|
|
|
|
| 344 |
return { hasOnnx, hasTokenizer, missingFiles: missing, missingReason: reason };
|
| 345 |
}
|
| 346 |
|
| 347 |
+
/**
|
| 348 |
+
* Determine if a model supports chat-style inputs/outputs.
|
| 349 |
+
* Uses pipeline_tag, tags, and name heuristics as fallback.
|
| 350 |
+
* @param {any} model
|
| 351 |
+
*/
|
| 352 |
+
function isModelChatCapable(model) {
|
| 353 |
+
if (!model) return false;
|
| 354 |
+
const allowedPipelines = new Set(['text-generation', 'conversational', 'text2text-generation', 'chat']);
|
| 355 |
+
if (model.pipeline_tag && allowedPipelines.has(model.pipeline_tag)) return true;
|
| 356 |
+
// tags array may contain 'conversational' or 'chat'
|
| 357 |
+
if (Array.isArray(model.tags)) {
|
| 358 |
+
for (const t of model.tags) {
|
| 359 |
+
if (typeof t === 'string' && allowedPipelines.has(t)) return true;
|
| 360 |
+
}
|
| 361 |
+
}
|
| 362 |
+
// fallback heuristics in id/name: look for chat, conversational, dialog, instruct
|
| 363 |
+
const id = (model.id || '').toLowerCase();
|
| 364 |
+
const name = (model.name || '').toLowerCase();
|
| 365 |
+
const heuristics = ['chat', 'conversational', 'dialog', 'instruct', 'instruction'];
|
| 366 |
+
for (const h of heuristics) {
|
| 367 |
+
if (id.includes(h) || name.includes(h)) return true;
|
| 368 |
+
}
|
| 369 |
+
return false;
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
/**
|
| 373 |
* Get fallback models if API fetch fails
|
| 374 |
* @returns {ModelInfo[]}
|
src/worker/boot-worker.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ModelCache } from './model-cache';
|
|
| 4 |
|
| 5 |
export function bootWorker() {
|
| 6 |
const modelCache = new ModelCache();
|
|
|
|
| 7 |
// Report starting
|
| 8 |
try {
|
| 9 |
self.postMessage({ type: 'status', status: 'initializing' });
|
|
@@ -29,6 +30,7 @@ export function bootWorker() {
|
|
| 29 |
const { modelName = modelCache.knownModels[0] } = data;
|
| 30 |
try {
|
| 31 |
const pipe = await modelCache.getModel({ modelName });
|
|
|
|
| 32 |
self.postMessage({ id, type: 'response', result: { model: modelName, status: 'loaded' } });
|
| 33 |
} catch (err) {
|
| 34 |
self.postMessage({ id, type: 'error', error: String(err) });
|
|
@@ -43,7 +45,7 @@ export function bootWorker() {
|
|
| 43 |
}
|
| 44 |
}
|
| 45 |
|
| 46 |
-
async function handleRunPrompt({ prompt, modelName =
|
| 47 |
try {
|
| 48 |
const pipe = await modelCache.getModel({ modelName });
|
| 49 |
// run the pipeline
|
|
|
|
| 4 |
|
| 5 |
export function bootWorker() {
|
| 6 |
const modelCache = new ModelCache();
|
| 7 |
+
let selectedModel = modelCache.knownModels[0];
|
| 8 |
// Report starting
|
| 9 |
try {
|
| 10 |
self.postMessage({ type: 'status', status: 'initializing' });
|
|
|
|
| 30 |
const { modelName = modelCache.knownModels[0] } = data;
|
| 31 |
try {
|
| 32 |
const pipe = await modelCache.getModel({ modelName });
|
| 33 |
+
selectedModel = modelName;
|
| 34 |
self.postMessage({ id, type: 'response', result: { model: modelName, status: 'loaded' } });
|
| 35 |
} catch (err) {
|
| 36 |
self.postMessage({ id, type: 'error', error: String(err) });
|
|
|
|
| 45 |
}
|
| 46 |
}
|
| 47 |
|
| 48 |
+
async function handleRunPrompt({ prompt, modelName = selectedModel, id, options }) {
|
| 49 |
try {
|
| 50 |
const pipe = await modelCache.getModel({ modelName });
|
| 51 |
// run the pipeline
|