mihailik commited on
Commit
f40bfec
·
1 Parent(s): 3ed38da

Tweaking model list a little.

Browse files
Files changed (2) hide show
  1. package.json +1 -1
  2. src/app/model-list.js +34 -31
package.json CHANGED
@@ -1,6 +1,6 @@
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",
 
1
  {
2
  "name": "localm",
3
+ "version": "1.1.21",
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
@@ -56,20 +56,20 @@ export async function fetchBrowserModels() {
56
  // eslint-disable-next-line no-await-in-loop
57
  const res = await fetch(url);
58
  if (!res.ok) {
59
- console.warn(`HF batch ${i+1} returned ${res.status}; stopping further batches`);
60
  break;
61
  }
62
  // eslint-disable-next-line no-await-in-loop
63
  const batch = await res.json();
64
  if (!Array.isArray(batch) || batch.length === 0) {
65
- console.log(`HF batch ${i+1} returned 0 models; stopping`);
66
  break;
67
  }
68
- console.log(`batch ${i+1} -> ${batch.length} models`);
69
  allRaw = allRaw.concat(batch);
70
  if (batch.length < batchSize) break; // last page
71
  } catch (err) {
72
- console.warn(`Error fetching HF batch ${i+1}:`, err);
73
  break;
74
  }
75
  }
@@ -92,22 +92,22 @@ export async function fetchBrowserModels() {
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));
108
 
109
- const auth = withFiles.filter(m => m && m.requiresAuth).slice(0, 10).map(x => x);
110
- const pub = withFiles.filter(m => m && !m.requiresAuth).slice(0, 10).map(x => x);
111
 
112
  const final = [...auth, ...pub];
113
 
@@ -132,26 +132,26 @@ export async function fetchBrowserModels() {
132
  function isModelMobileCapable(model) {
133
  // Skip if no model ID
134
  if (!model.id) return false;
135
-
136
  // Estimate model size from various indicators
137
  const sizeEstimate = estimateModelSize(model);
138
-
139
  // Skip models that are too large
140
  if (sizeEstimate > MOBILE_SIZE_THRESHOLD) {
141
  return false;
142
  }
143
-
144
  // Prefer models with certain pipeline tags that work well in browsers
145
  const preferredTags = [
146
  'text-generation',
147
- 'text2text-generation',
148
  'feature-extraction',
149
  'sentence-similarity',
150
  'fill-mask'
151
  ];
152
-
153
  const hasPreferredTag = !model.pipeline_tag || preferredTags.includes(model.pipeline_tag);
154
-
155
  // Skip certain model types that are less suitable for general text generation
156
  const excludePatterns = [
157
  /whisper/i,
@@ -162,9 +162,9 @@ function isModelMobileCapable(model) {
162
  /classification/i,
163
  /embedding/i
164
  ];
165
-
166
  const isExcluded = excludePatterns.some(pattern => pattern.test(model.id));
167
-
168
  return hasPreferredTag && !isExcluded;
169
  }
170
 
@@ -175,14 +175,14 @@ function isModelMobileCapable(model) {
175
  */
176
  function estimateModelSize(model) {
177
  const modelId = model.id.toLowerCase();
178
-
179
  // Extract size from model name patterns
180
  const sizePatterns = [
181
  /(\d+\.?\d*)b\b/i, // "7b", "3.8b", etc.
182
  /(\d+)m\b/i, // "125m" -> convert to billions
183
  /(\d+)k\b/i // "125k" -> very small
184
  ];
185
-
186
  for (const pattern of sizePatterns) {
187
  const match = modelId.match(pattern);
188
  if (match) {
@@ -196,7 +196,7 @@ function estimateModelSize(model) {
196
  }
197
  }
198
  }
199
-
200
  // If no size found in name, make conservative estimates based on model family
201
  if (modelId.includes('gpt2') || modelId.includes('distil')) return 0.2;
202
  if (modelId.includes('phi-1') || modelId.includes('phi1')) return 1.3;
@@ -206,7 +206,7 @@ function estimateModelSize(model) {
206
  if (modelId.includes('qwen') && modelId.includes('7b')) return 7;
207
  if (modelId.includes('llama') && modelId.includes('7b')) return 7;
208
  if (modelId.includes('llama') && modelId.includes('13b')) return 13;
209
-
210
  // Default conservative estimate for unknown models
211
  return 5;
212
  }
@@ -222,7 +222,7 @@ function processModelData(model) {
222
  const vendor = extractVendor(model.id);
223
  const name = extractModelName(model.id);
224
  const slashCommand = generateSlashCommand(model.id);
225
-
226
  return {
227
  id: model.id,
228
  name,
@@ -272,7 +272,7 @@ function extractVendor(modelId) {
272
  function extractModelName(modelId) {
273
  const parts = modelId.split('/');
274
  const name = parts[parts.length - 1];
275
-
276
  // Clean up common patterns
277
  return name
278
  .replace(/-ONNX$/, '')
@@ -291,7 +291,7 @@ function extractModelName(modelId) {
291
  */
292
  function generateSlashCommand(modelId) {
293
  const name = (modelId.split('/').pop() || modelId).toLowerCase();
294
-
295
  // Create short, memorable commands
296
  if (name.includes('phi-3') || name.includes('phi3')) return 'phi3';
297
  if (name.includes('phi-1') || name.includes('phi1')) return 'phi1';
@@ -304,7 +304,7 @@ function generateSlashCommand(modelId) {
304
  if (name.includes('llama')) return 'llama';
305
  if (name.includes('gemma')) return 'gemma';
306
  if (name.includes('flan')) return 'flant5';
307
-
308
  // Generate from first few characters of model name
309
  const clean = name.replace(/[^a-z0-9]/g, '');
310
  return clean.substring(0, 8);
@@ -351,7 +351,10 @@ function detectRequiredFiles(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)) {
@@ -362,7 +365,7 @@ function isModelChatCapable(model) {
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
  }
@@ -386,7 +389,7 @@ function getFallbackModels() {
386
  {
387
  id: 'mistralai/Mistral-7B-v0.1',
388
  name: 'Mistral 7B',
389
- vendor: 'Mistral AI',
390
  size: '7.3B',
391
  slashCommand: 'mistral',
392
  description: 'Highly efficient, outperforms larger models with innovative architecture'
 
56
  // eslint-disable-next-line no-await-in-loop
57
  const res = await fetch(url);
58
  if (!res.ok) {
59
+ console.warn(`HF batch ${i + 1} returned ${res.status}; stopping further batches`);
60
  break;
61
  }
62
  // eslint-disable-next-line no-await-in-loop
63
  const batch = await res.json();
64
  if (!Array.isArray(batch) || batch.length === 0) {
65
+ console.log(`HF batch ${i + 1} returned 0 models; stopping`);
66
  break;
67
  }
68
+ console.log(`batch ${i + 1} -> ${batch.length} models`);
69
  allRaw = allRaw.concat(batch);
70
  if (batch.length < batchSize) break; // last page
71
  } catch (err) {
72
+ console.warn(`Error fetching HF batch ${i + 1}:`, err);
73
  break;
74
  }
75
  }
 
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));
108
 
109
+ const auth = withFiles.filter(m => m && m.requiresAuth).slice(0, 10).map(x => x);
110
+ const pub = withFiles.filter(m => m && !m.requiresAuth).slice(0, 10).map(x => x);
111
 
112
  const final = [...auth, ...pub];
113
 
 
132
  function isModelMobileCapable(model) {
133
  // Skip if no model ID
134
  if (!model.id) return false;
135
+
136
  // Estimate model size from various indicators
137
  const sizeEstimate = estimateModelSize(model);
138
+
139
  // Skip models that are too large
140
  if (sizeEstimate > MOBILE_SIZE_THRESHOLD) {
141
  return false;
142
  }
143
+
144
  // Prefer models with certain pipeline tags that work well in browsers
145
  const preferredTags = [
146
  'text-generation',
147
+ 'text2text-generation',
148
  'feature-extraction',
149
  'sentence-similarity',
150
  'fill-mask'
151
  ];
152
+
153
  const hasPreferredTag = !model.pipeline_tag || preferredTags.includes(model.pipeline_tag);
154
+
155
  // Skip certain model types that are less suitable for general text generation
156
  const excludePatterns = [
157
  /whisper/i,
 
162
  /classification/i,
163
  /embedding/i
164
  ];
165
+
166
  const isExcluded = excludePatterns.some(pattern => pattern.test(model.id));
167
+
168
  return hasPreferredTag && !isExcluded;
169
  }
170
 
 
175
  */
176
  function estimateModelSize(model) {
177
  const modelId = model.id.toLowerCase();
178
+
179
  // Extract size from model name patterns
180
  const sizePatterns = [
181
  /(\d+\.?\d*)b\b/i, // "7b", "3.8b", etc.
182
  /(\d+)m\b/i, // "125m" -> convert to billions
183
  /(\d+)k\b/i // "125k" -> very small
184
  ];
185
+
186
  for (const pattern of sizePatterns) {
187
  const match = modelId.match(pattern);
188
  if (match) {
 
196
  }
197
  }
198
  }
199
+
200
  // If no size found in name, make conservative estimates based on model family
201
  if (modelId.includes('gpt2') || modelId.includes('distil')) return 0.2;
202
  if (modelId.includes('phi-1') || modelId.includes('phi1')) return 1.3;
 
206
  if (modelId.includes('qwen') && modelId.includes('7b')) return 7;
207
  if (modelId.includes('llama') && modelId.includes('7b')) return 7;
208
  if (modelId.includes('llama') && modelId.includes('13b')) return 13;
209
+
210
  // Default conservative estimate for unknown models
211
  return 5;
212
  }
 
222
  const vendor = extractVendor(model.id);
223
  const name = extractModelName(model.id);
224
  const slashCommand = generateSlashCommand(model.id);
225
+
226
  return {
227
  id: model.id,
228
  name,
 
272
  function extractModelName(modelId) {
273
  const parts = modelId.split('/');
274
  const name = parts[parts.length - 1];
275
+
276
  // Clean up common patterns
277
  return name
278
  .replace(/-ONNX$/, '')
 
291
  */
292
  function generateSlashCommand(modelId) {
293
  const name = (modelId.split('/').pop() || modelId).toLowerCase();
294
+
295
  // Create short, memorable commands
296
  if (name.includes('phi-3') || name.includes('phi3')) return 'phi3';
297
  if (name.includes('phi-1') || name.includes('phi1')) return 'phi1';
 
304
  if (name.includes('llama')) return 'llama';
305
  if (name.includes('gemma')) return 'gemma';
306
  if (name.includes('flan')) return 'flant5';
307
+
308
  // Generate from first few characters of model name
309
  const clean = name.replace(/[^a-z0-9]/g, '');
310
  return clean.substring(0, 8);
 
351
  */
352
  function isModelChatCapable(model) {
353
  if (!model) return false;
354
+ const allowedPipelines = new Set([
355
+ 'text-generation', 'conversational', 'text2text-generation', 'chat',
356
+ 'sentence'
357
+ ]);
358
  if (model.pipeline_tag && allowedPipelines.has(model.pipeline_tag)) return true;
359
  // tags array may contain 'conversational' or 'chat'
360
  if (Array.isArray(model.tags)) {
 
365
  // fallback heuristics in id/name: look for chat, conversational, dialog, instruct
366
  const id = (model.id || '').toLowerCase();
367
  const name = (model.name || '').toLowerCase();
368
+ const heuristics = ['chat', 'conversational', 'dialog', 'instruct', 'instruction', 'sentence'];
369
  for (const h of heuristics) {
370
  if (id.includes(h) || name.includes(h)) return true;
371
  }
 
389
  {
390
  id: 'mistralai/Mistral-7B-v0.1',
391
  name: 'Mistral 7B',
392
+ vendor: 'Mistral AI',
393
  size: '7.3B',
394
  slashCommand: 'mistral',
395
  description: 'Highly efficient, outperforms larger models with innovative architecture'