1// compare/models.js â model classification, fetching, display names, persistence 2import Storage from '../storage.js'; 3import state from './state.js'; 4import uiModule from '../ui.js'; 5import { sortModelObjects } from '../modelSort.js'; 6 7var escapeHtml = uiModule.esc; 8 9// ââ Model classification constants ââ 10const NON_CHAT_PREFIXES = ['tts-', 'whisper-', 'text-embedding-', 'text-moderation-', 'moderation-', 'embedding']; 11const NON_CHAT_SUFFIXES = ['deep-research', '-online']; 12const IMAGE_PREFIXES = ['dall-e-3', 'gpt-image', 'chatgpt-image']; 13const DEPRECATED_IMAGE = ['dall-e-2']; 14 15function classifyModel(id) { 16 const lower = id.toLowerCase(); 17 if (DEPRECATED_IMAGE.some(p => lower.startsWith(p))) return 'other'; 18 if (IMAGE_PREFIXES.some(p => lower.startsWith(p))) return 'image'; 19 if (NON_CHAT_PREFIXES.some(p => lower.startsWith(p))) return 'other'; 20 if (NON_CHAT_SUFFIXES.some(p => lower.endsWith(p) || lower.includes(p))) return 'other'; 21 return 'chat'; 22} 23 24/** Build display names for selected models, adding endpoint name when the same model appears from multiple providers. */ 25function _modelDisplayNames(models) { 26 const nameCount = {}; 27 for (const m of models) { 28 const short = m.name || m.model.split('/').pop(); 29 nameCount[short] = (nameCount[short] || 0) + 1; 30 } 31 return models.map(m => { 32 const short = m.name || m.model.split('/').pop(); 33 if (nameCount[short] > 1 && m.endpointName) return short + ' (' + escapeHtml(m.endpointName) + ')'; 34 return short; 35 }); 36} 37 38/** Save selected models and synth models to localStorage, keyed by compare mode. */ 39function _persistSelections() { 40 if (state._selectedModels.length > 0) { 41 Storage.setJSON('odysseus-compare-selections-' + (state._compareMode || 'chat'), state._selectedModels); 42 } 43 if ((state._compareMode === 'search' || state._compareMode === 'research') && state._searchSynthModels) { 44 Storage.setJSON('odysseus-compare-synth-' + state._compareMode, state._searchSynthModels); 45 } 46} 47 48// ââ Model fetching with cache ââ 49const MODELS_CACHE_TTL = 30000; // 30 seconds 50 51/** Fetch available models from API. */ 52async function fetchModels() { 53 const now = Date.now(); 54 if (state._fetchModelsCache && (now - state._fetchModelsCacheTime) < MODELS_CACHE_TTL) { 55 return state._fetchModelsCache; 56 } 57 const res = await fetch(`${state.API_BASE}/api/models`); 58 const data = await res.json(); 59 const models = []; 60 if (data.items && data.items.length > 0) { 61 data.items.forEach(item => { 62 const displayNames = item.models_display || item.models || []; 63 const extraDisplay = item.models_extra_display || item.models_extra || []; 64 // Curated list (item.models) takes priority; non-curated extras come 65 // after so newer/uncatalogued models (e.g. deepseek-v4-pro) still show. 66 (item.models || []).forEach((mid, i) => { 67 models.push({ 68 id: mid, 69 url: item.url, 70 name: (displayNames[i] || mid).split('/').pop(), 71 endpointId: item.endpoint_id || null, 72 endpointName: item.endpoint_name || '', 73 type: classifyModel(mid), 74 }); 75 }); 76 (item.models_extra || []).forEach((mid, i) => { 77 models.push({ 78 id: mid, 79 url: item.url, 80 name: (extraDisplay[i] || mid).split('/').pop(), 81 endpointId: item.endpoint_id || null, 82 endpointName: item.endpoint_name || '', 83 type: classifyModel(mid), 84 }); 85 }); 86 }); 87 } 88 state._fetchModelsCache = sortModelObjects(models); 89 state._fetchModelsCacheTime = now; 90 return state._fetchModelsCache; 91} 92 93// ââ Shuffle pool persistence ââ 94const POOL_STORAGE_KEY = 'odysseus-shuffle-pool-excluded'; 95 96function getExcludedModels() { 97 return Storage.getJSON(POOL_STORAGE_KEY, []); 98} 99 100function setExcludedModels(arr) { 101 Storage.setJSON(POOL_STORAGE_KEY, arr); 102} 103 104export { classifyModel, _modelDisplayNames, fetchModels, _persistSelections, getExcludedModels, setExcludedModels };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.