llm-switcher 1.6.3 → 1.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/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/ui.html +107 -104
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog — LLM Switcher
|
|
2
2
|
|
|
3
|
+
## Release 1.6.4 — 2026-10-08
|
|
4
|
+
|
|
5
|
+
- **Dynamic Model Provider Source for Idle Compaction:** Query live model listings directly from the selected profile's `baseURL` via `/models` instead of hardcoding provider URLs or static model lists.
|
|
6
|
+
- **Clean Model Combobox without Slot Aliases:** Eliminate hardcoded quick preset buttons, popular model arrays, and slot aliases from the summarization model combobox. Models are loaded dynamically from the upstream provider, sorted alphabetically with context limits, with support for instant search filtering and deterministic heuristic truncation (`""`).
|
|
7
|
+
|
|
3
8
|
## Release 1.6.3 — 2026-10-08
|
|
4
9
|
|
|
5
10
|
- **Interactive Summarization Model Selection & Presets:** Replace raw text input for Idle Compaction's summarization model with a fully searchable combobox dropdown and quick 1-click preset chips. Users can pick directly from deterministic heuristic mode (`""`), slot aliases (`haiku`, `subagent`, `sonnet`, `main`), configured profile models (`zencore/*`, `codex/*`), and discovered catalog models, or type to filter in real-time. Selection automatically saves and updates active highlights across both light and dark themes.
|
package/package.json
CHANGED
package/ui.html
CHANGED
|
@@ -1105,12 +1105,23 @@
|
|
|
1105
1105
|
</div>
|
|
1106
1106
|
</div>
|
|
1107
1107
|
|
|
1108
|
+
<div class="row">
|
|
1109
|
+
<div class="who">
|
|
1110
|
+
<div class="l">Model Provider Source</div>
|
|
1111
|
+
<div class="meta" id="idle-compact-source-meta">Profile Base URL queried via /models to list upstream models</div>
|
|
1112
|
+
</div>
|
|
1113
|
+
<div style="display:flex; align-items:center; gap:0.5rem; margin-left:auto; min-width:18rem; max-width:26rem;">
|
|
1114
|
+
<select id="idle-compact-source-profile" onchange="onCompactSourceProfileChange()" style="flex:1; font-size:0.8rem;"></select>
|
|
1115
|
+
<button type="button" class="btn btn-outline btn-sm" id="btn-reload-compact-models" onclick="reloadCompactModels(true)" title="Fetch live models from provider Base URL">Reload</button>
|
|
1116
|
+
</div>
|
|
1117
|
+
</div>
|
|
1118
|
+
|
|
1108
1119
|
<div class="row" style="align-items:flex-start;">
|
|
1109
1120
|
<div class="who" style="padding-top:0.25rem;">
|
|
1110
1121
|
<div class="l">Summarization Model</div>
|
|
1111
|
-
<div class="meta">Select from
|
|
1122
|
+
<div class="meta" id="idle-compact-models-status">Select model from provider URL or enter custom ID (empty = deterministic truncation)</div>
|
|
1112
1123
|
</div>
|
|
1113
|
-
<div style="min-width:18rem; flex:1; max-width:
|
|
1124
|
+
<div style="min-width:18rem; flex:1; max-width:26rem; margin-left:auto;">
|
|
1114
1125
|
<div class="combo" id="combo-compact-model">
|
|
1115
1126
|
<input type="text" id="idle-compact-model" placeholder="empty = deterministic truncation" maxlength="512" onchange="saveIdleCompact()" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="idle-compact-model-list" autocomplete="off" spellcheck="false" style="font-family:var(--font-mono); font-size:0.8rem;" title="Upstream model used to generate turn summary text">
|
|
1116
1127
|
<button type="button" class="combo-toggle" tabindex="-1" aria-label="Show available summary models">
|
|
@@ -1118,9 +1129,6 @@
|
|
|
1118
1129
|
</button>
|
|
1119
1130
|
<ul class="combo-list" id="idle-compact-model-list" role="listbox" aria-label="Available summarization models" hidden></ul>
|
|
1120
1131
|
</div>
|
|
1121
|
-
<div id="compact-model-presets" style="display:flex; flex-wrap:wrap; align-items:center; gap:0.35rem;">
|
|
1122
|
-
<!-- Quick Presets rendered dynamically -->
|
|
1123
|
-
</div>
|
|
1124
1132
|
</div>
|
|
1125
1133
|
</div>
|
|
1126
1134
|
|
|
@@ -2072,10 +2080,7 @@
|
|
|
2072
2080
|
const elKb = document.getElementById('idle-compact-kb');
|
|
2073
2081
|
if (elKb) elKb.value = (c.minBytes ?? 65536) / 1024;
|
|
2074
2082
|
const elModel = document.getElementById('idle-compact-model');
|
|
2075
|
-
if (elModel)
|
|
2076
|
-
elModel.value = c.model || '';
|
|
2077
|
-
updateCompactModelPresets(c.model || '');
|
|
2078
|
-
}
|
|
2083
|
+
if (elModel) elModel.value = c.model || '';
|
|
2079
2084
|
const elCodex = document.getElementById('idle-compact-codex');
|
|
2080
2085
|
if (elCodex) elCodex.checked = !!c.codex;
|
|
2081
2086
|
const elKeep = document.getElementById('idle-compact-keep');
|
|
@@ -2125,66 +2130,107 @@
|
|
|
2125
2130
|
}
|
|
2126
2131
|
}
|
|
2127
2132
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
return
|
|
2133
|
+
let compactLoadedModels = [];
|
|
2134
|
+
let compactLoadedLimits = {};
|
|
2135
|
+
let compactSourceProfileKey = '';
|
|
2136
|
+
|
|
2137
|
+
function populateCompactSourceProfiles() {
|
|
2138
|
+
const sel = document.getElementById('idle-compact-source-profile');
|
|
2139
|
+
if (!sel) return;
|
|
2140
|
+
const profiles = state.config?.profiles || {};
|
|
2141
|
+
const activeClaude = state.config?.activeProfiles?.claude;
|
|
2142
|
+
const activeCodex = state.config?.activeProfiles?.codex;
|
|
2143
|
+
const keys = Object.keys(profiles);
|
|
2144
|
+
|
|
2145
|
+
if (!compactSourceProfileKey || !profiles[compactSourceProfileKey]) {
|
|
2146
|
+
compactSourceProfileKey = activeClaude || activeCodex || keys[0] || '';
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
if (!keys.length) {
|
|
2150
|
+
sel.innerHTML = '<option value="">(No profiles configured)</option>';
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
sel.innerHTML = keys.map(k => {
|
|
2155
|
+
const p = profiles[k];
|
|
2156
|
+
const isActive = (k === activeClaude || k === activeCodex);
|
|
2157
|
+
const prefix = isActive ? '★ ' : '';
|
|
2158
|
+
const selected = (k === compactSourceProfileKey) ? 'selected' : '';
|
|
2159
|
+
return `<option value="${escapeHtml(k)}" ${selected}>${escapeHtml(prefix + (p.name || k))} (${escapeHtml(p.baseURL || 'no URL')})</option>`;
|
|
2160
|
+
}).join('');
|
|
2161
|
+
|
|
2162
|
+
updateCompactSourceMeta();
|
|
2135
2163
|
}
|
|
2136
2164
|
|
|
2137
|
-
function
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
updateCompactModelPresets(model);
|
|
2165
|
+
function updateCompactSourceMeta() {
|
|
2166
|
+
const meta = document.getElementById('idle-compact-source-meta');
|
|
2167
|
+
const p = state.config?.profiles?.[compactSourceProfileKey];
|
|
2168
|
+
if (meta && p) {
|
|
2169
|
+
meta.textContent = `${p.baseURL || 'No Base URL configured'} · ${p.outFormat || p.mode || 'convert'}`;
|
|
2143
2170
|
}
|
|
2144
2171
|
}
|
|
2145
2172
|
|
|
2146
|
-
function
|
|
2147
|
-
const
|
|
2148
|
-
if (
|
|
2149
|
-
|
|
2150
|
-
|
|
2173
|
+
async function onCompactSourceProfileChange() {
|
|
2174
|
+
const sel = document.getElementById('idle-compact-source-profile');
|
|
2175
|
+
if (sel) {
|
|
2176
|
+
compactSourceProfileKey = sel.value;
|
|
2177
|
+
updateCompactSourceMeta();
|
|
2178
|
+
await reloadCompactModels(true);
|
|
2151
2179
|
}
|
|
2180
|
+
}
|
|
2152
2181
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2182
|
+
async function reloadCompactModels(force = false) {
|
|
2183
|
+
const p = state.config?.profiles?.[compactSourceProfileKey];
|
|
2184
|
+
const statusEl = document.getElementById('idle-compact-models-status');
|
|
2185
|
+
const reloadBtn = document.getElementById('btn-reload-compact-models');
|
|
2186
|
+
if (!p || !p.baseURL) {
|
|
2187
|
+
compactLoadedModels = [];
|
|
2188
|
+
compactLoadedLimits = {};
|
|
2189
|
+
if (statusEl) statusEl.textContent = 'Configure a Base URL on the profile to load live models';
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2158
2192
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2193
|
+
if (statusEl) statusEl.textContent = `Loading models from ${p.baseURL}...`;
|
|
2194
|
+
if (reloadBtn) reloadBtn.disabled = true;
|
|
2195
|
+
|
|
2196
|
+
try {
|
|
2197
|
+
const res = await api('/api/fetch-models', {
|
|
2198
|
+
method: 'POST',
|
|
2199
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2200
|
+
body: JSON.stringify({ baseURL: p.baseURL, apiKey: MASKED_KEY, key: compactSourceProfileKey })
|
|
2201
|
+
});
|
|
2202
|
+
const data = await res.json();
|
|
2203
|
+
if (data.ok && Array.isArray(data.models)) {
|
|
2204
|
+
compactLoadedModels = data.models;
|
|
2205
|
+
compactLoadedLimits = data.limits || {};
|
|
2206
|
+
if (statusEl) {
|
|
2207
|
+
statusEl.textContent = `${compactLoadedModels.length} models loaded from ${p.baseURL}. Select from list or enter model ID.`;
|
|
2208
|
+
}
|
|
2209
|
+
} else {
|
|
2210
|
+
compactLoadedModels = [];
|
|
2211
|
+
compactLoadedLimits = {};
|
|
2212
|
+
if (statusEl) {
|
|
2213
|
+
statusEl.textContent = `Models not loaded: ${modelsError(data.error)}`;
|
|
2168
2214
|
}
|
|
2169
2215
|
}
|
|
2216
|
+
} catch (err) {
|
|
2217
|
+
compactLoadedModels = [];
|
|
2218
|
+
compactLoadedLimits = {};
|
|
2219
|
+
if (statusEl) statusEl.textContent = `Failed to load models: ${err.message}`;
|
|
2220
|
+
} finally {
|
|
2221
|
+
if (reloadBtn) reloadBtn.disabled = false;
|
|
2222
|
+
const combo = document.getElementById('combo-compact-model');
|
|
2223
|
+
if (combo && combo.hasAttribute('data-open')) renderComboOptions(combo);
|
|
2170
2224
|
}
|
|
2171
|
-
|
|
2172
|
-
container.innerHTML = `<span class="hint" style="font-size:0.75rem; align-self:center; margin-right:0.2rem;">Quick Presets:</span>` +
|
|
2173
|
-
presets.map(p => {
|
|
2174
|
-
const active = (p.id === currentModel);
|
|
2175
|
-
const style = active
|
|
2176
|
-
? 'padding:0.18rem 0.5rem; font-size:0.75rem; border-color:var(--accent); color:var(--accent); background:var(--accent-soft); font-weight:600;'
|
|
2177
|
-
: 'padding:0.18rem 0.5rem; font-size:0.75rem;';
|
|
2178
|
-
return `<button type="button" class="btn btn-outline btn-sm" style="${style}" onclick="setCompactModel('${escapeHtml(p.id)}')">${escapeHtml(p.label)}</button>`;
|
|
2179
|
-
}).join('');
|
|
2180
2225
|
}
|
|
2181
2226
|
|
|
2182
2227
|
async function loadIdleCompact() {
|
|
2183
|
-
|
|
2228
|
+
populateCompactSourceProfiles();
|
|
2184
2229
|
try {
|
|
2185
2230
|
const r = await api('/api/idle-compact');
|
|
2186
2231
|
if (r.ok) showIdleCompact((await r.json()).idleCompact);
|
|
2187
2232
|
} catch { /* the card stays at its defaults; nothing here is required to route */ }
|
|
2233
|
+
await reloadCompactModels();
|
|
2188
2234
|
}
|
|
2189
2235
|
|
|
2190
2236
|
async function saveIdleCompact() {
|
|
@@ -2861,9 +2907,6 @@
|
|
|
2861
2907
|
combo.dataset.filter = '1';
|
|
2862
2908
|
combo.activeIndex = -1;
|
|
2863
2909
|
openCombo(combo);
|
|
2864
|
-
if (input.id === 'idle-compact-model') {
|
|
2865
|
-
updateCompactModelPresets(input.value.trim());
|
|
2866
|
-
}
|
|
2867
2910
|
});
|
|
2868
2911
|
input.addEventListener('keydown', onComboKeydown);
|
|
2869
2912
|
input.addEventListener('blur', () => closeCombo(combo));
|
|
@@ -2971,54 +3014,16 @@
|
|
|
2971
3014
|
// 1. Deterministic option (empty string)
|
|
2972
3015
|
add('', 'Deterministic Truncation (no external call)', 'Pure heuristic context truncation · Zero latency & zero tokens', 'Deterministic');
|
|
2973
3016
|
|
|
2974
|
-
// 2.
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
const profiles = state.config?.profiles || {};
|
|
2982
|
-
for (const [profKey, p] of Object.entries(profiles)) {
|
|
2983
|
-
const profName = p.name || profKey;
|
|
2984
|
-
if (p.defaultModels) {
|
|
2985
|
-
for (const [slot, m] of Object.entries(p.defaultModels)) {
|
|
2986
|
-
if (m) add(m, m, `${profName} · ${slot} slot`, 'Profile Models');
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
if (Array.isArray(p.publicModels)) {
|
|
2990
|
-
for (const m of p.publicModels) {
|
|
2991
|
-
if (m) add(m, m, `${profName}`, 'Profile Models');
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
|
|
2996
|
-
// 4. Discovered Catalog models
|
|
2997
|
-
if (cachedCatalog) {
|
|
2998
|
-
if (Array.isArray(cachedCatalog.claude?.models)) {
|
|
2999
|
-
for (const m of cachedCatalog.claude.models) {
|
|
3000
|
-
if (m.id) add(m.id, m.id, `Claude Catalog · ${m.tier || ''}`, 'Catalog');
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
if (Array.isArray(cachedCatalog.codex?.models)) {
|
|
3004
|
-
for (const m of cachedCatalog.codex.models) {
|
|
3005
|
-
if (m.id) add(m.id, m.id, `Codex Catalog · ${m.role || ''}`, 'Catalog');
|
|
3006
|
-
}
|
|
3017
|
+
// 2. Models fetched live from the provider Base URL
|
|
3018
|
+
const prof = state.config?.profiles?.[compactSourceProfileKey];
|
|
3019
|
+
if (Array.isArray(compactLoadedModels) && compactLoadedModels.length) {
|
|
3020
|
+
for (const m of compactLoadedModels) {
|
|
3021
|
+
const l = compactLoadedLimits[m];
|
|
3022
|
+
const info = l?.context ? `${Math.round(l.context / 1000)}k context` : (prof?.name || 'Upstream');
|
|
3023
|
+
add(m, m, info, 'Upstream Model');
|
|
3007
3024
|
}
|
|
3008
3025
|
}
|
|
3009
3026
|
|
|
3010
|
-
// 5. Popular lightweight models
|
|
3011
|
-
const popular = [
|
|
3012
|
-
{ id: 'zencore/muse-spark-1.3-contributor-free', desc: 'ZenCore contributor fast model' },
|
|
3013
|
-
{ id: 'zencore/exo-free', desc: 'ZenCore free pool fast model' },
|
|
3014
|
-
{ id: 'claude-3-5-haiku-20241022', desc: 'Anthropic lightweight summarizer' },
|
|
3015
|
-
{ id: 'gpt-4o-mini', desc: 'OpenAI low-cost summarizer' },
|
|
3016
|
-
{ id: 'gemini-2.5-flash', desc: 'Google Gemini fast summarizer' }
|
|
3017
|
-
];
|
|
3018
|
-
for (const p of popular) {
|
|
3019
|
-
add(p.id, p.id, p.desc, 'Recommended');
|
|
3020
|
-
}
|
|
3021
|
-
|
|
3022
3027
|
return items;
|
|
3023
3028
|
}
|
|
3024
3029
|
|
|
@@ -3030,13 +3035,13 @@
|
|
|
3030
3035
|
const filtered = allItems.filter(item => {
|
|
3031
3036
|
if (!q) return true;
|
|
3032
3037
|
if (item.id === '' && ('deterministic'.includes(q) || 'none'.includes(q) || 'empty'.includes(q) || 'heuristic'.includes(q))) return true;
|
|
3033
|
-
return item.id.toLowerCase().includes(q) || item.label.toLowerCase().includes(q)
|
|
3038
|
+
return item.id.toLowerCase().includes(q) || item.label.toLowerCase().includes(q);
|
|
3034
3039
|
}).slice(0, 150);
|
|
3035
3040
|
|
|
3036
3041
|
combo.matches = filtered.map(item => item.id);
|
|
3037
3042
|
|
|
3038
3043
|
if (!filtered.length) {
|
|
3039
|
-
list.innerHTML = '<li class="combo-empty">No matching model found. The typed model will be used directly.</li>';
|
|
3044
|
+
list.innerHTML = '<li class="combo-empty">No matching model found. The typed model ID will be used directly.</li>';
|
|
3040
3045
|
return;
|
|
3041
3046
|
}
|
|
3042
3047
|
|
|
@@ -3048,8 +3053,7 @@
|
|
|
3048
3053
|
const isSelected = item.id === current;
|
|
3049
3054
|
let badgeClass = 'badge outline';
|
|
3050
3055
|
if (item.category === 'Deterministic') badgeClass = 'badge ok';
|
|
3051
|
-
else if (item.category === '
|
|
3052
|
-
else if (item.category === 'Profile Models') badgeClass = 'badge';
|
|
3056
|
+
else if (item.category === 'Upstream Model') badgeClass = 'badge';
|
|
3053
3057
|
|
|
3054
3058
|
const cut = item.id.lastIndexOf('/') + 1;
|
|
3055
3059
|
const prefix = cut ? `<span class="pfx">${markMatch(item.id.slice(0, cut), q)}</span>` : '';
|
|
@@ -3098,7 +3102,6 @@
|
|
|
3098
3102
|
updateWindowNotes();
|
|
3099
3103
|
if (input.id === 'idle-compact-model') {
|
|
3100
3104
|
saveIdleCompact();
|
|
3101
|
-
updateCompactModelPresets(model);
|
|
3102
3105
|
}
|
|
3103
3106
|
}
|
|
3104
3107
|
|