mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-09 15:08:44 -04:00
Merge verified Odysseus fixes
This commit is contained in:
+50
-29
@@ -466,21 +466,22 @@ async function _selectAddedModelInChat(endpoint) {
|
||||
async function loadEndpoints() {
|
||||
const listLocal = el('adm-epList-local');
|
||||
const listApi = el('adm-epList-api');
|
||||
// Fallback to the legacy single list if the split containers don't exist
|
||||
// (older HTML or third-party embedding).
|
||||
const listLegacy = el('adm-epList');
|
||||
// Refresh model picker so new endpoints show up in chat
|
||||
if (window.modelsModule && window.modelsModule.refreshModels) {
|
||||
window.modelsModule.refreshModels(true);
|
||||
// Render endpoint rows first. Do not make Added Models wait on /api/models or
|
||||
// endpoint probes; explicit Refresh/Probe actions do that work.
|
||||
const refreshDependentModelUi = (force = false) => {
|
||||
setTimeout(() => {
|
||||
if (window.sessionModule && window.sessionModule.updateModelPicker) {
|
||||
window.sessionModule.updateModelPicker();
|
||||
if (window.modelsModule && window.modelsModule.refreshModels) {
|
||||
window.modelsModule.refreshModels(!!force, force ? {} : { cacheOnly: true }).then(() => {
|
||||
if (window.sessionModule && window.sessionModule.updateModelPicker) {
|
||||
window.sessionModule.updateModelPicker();
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
if (settingsModule && typeof settingsModule.refreshAiModelEndpoints === 'function') {
|
||||
settingsModule.refreshAiModelEndpoints();
|
||||
}
|
||||
if (settingsModule && typeof settingsModule.refreshAiModelEndpoints === 'function') {
|
||||
settingsModule.refreshAiModelEndpoints();
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
try {
|
||||
const res = await fetch('/api/model-endpoints', { credentials: 'same-origin' });
|
||||
// Treat a non-OK response (e.g. 401/403 for non-admins, or backend
|
||||
@@ -495,13 +496,16 @@ async function loadEndpoints() {
|
||||
const empty = '<div class="admin-empty">None</div>';
|
||||
if (listLocal) listLocal.innerHTML = empty;
|
||||
if (listApi) listApi.innerHTML = '<div class="admin-empty">None</div>';
|
||||
if (listLegacy) listLegacy.innerHTML = empty;
|
||||
refreshDependentModelUi();
|
||||
return;
|
||||
}
|
||||
const rowHtml = data.map(ep => {
|
||||
const epModels = Array.isArray(ep.models) ? ep.models : [];
|
||||
const visibleCount = epModels.length;
|
||||
const totalCount = visibleCount + (ep.hidden_count || 0);
|
||||
const pinnedModels = Array.isArray(ep.pinned_models) ? ep.pinned_models : [];
|
||||
const visibleCount = ep.picker_requires_pinning ? pinnedModels.length : epModels.length;
|
||||
const totalCount = Number.isFinite(Number(ep.model_count))
|
||||
? Number(ep.model_count)
|
||||
: visibleCount + (ep.hidden_count || 0);
|
||||
// `ep.models` is the *visible* set — when every model is hidden it's
|
||||
// empty, but we still need to render the expand panel so the user can
|
||||
// un-hide them. Gate on the total instead.
|
||||
@@ -562,17 +566,21 @@ async function loadEndpoints() {
|
||||
apiIdx.sort(_sortByEnabled);
|
||||
_renderInto(listLocal, localIdx);
|
||||
_renderInto(listApi, apiIdx);
|
||||
if (listLegacy) listLegacy.innerHTML = rowHtml.join('');
|
||||
// Iterate matching nodes across both containers.
|
||||
const queryAll = (sel) => {
|
||||
const out = [];
|
||||
[listLocal, listApi, listLegacy].forEach(c => {
|
||||
[listLocal, listApi].forEach(c => {
|
||||
if (c) c.querySelectorAll(sel).forEach(n => out.push(n));
|
||||
});
|
||||
return out;
|
||||
};
|
||||
queryAll('[data-adm-toggle-ep]').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => { e.stopPropagation(); await fetch(`/api/model-endpoints/${btn.dataset.admToggleEp}`, { method: 'PATCH' }); loadEndpoints(); });
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
await fetch(`/api/model-endpoints/${btn.dataset.admToggleEp}`, { method: 'PATCH' });
|
||||
await _refreshAfterEndpointChange();
|
||||
loadEndpoints();
|
||||
});
|
||||
});
|
||||
queryAll('[data-adm-copy-url]').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
@@ -663,6 +671,8 @@ async function loadEndpoints() {
|
||||
const _loadingHtml = (label) => `<span style="opacity:0.55;font-size:11px;display:inline-flex;align-items:center;gap:8px;">${esc(label)}</span>`;
|
||||
const renderModels = (models, warning = '') => {
|
||||
const sortedModels = sortModelObjects(models);
|
||||
const usesPinnedPicker = sortedModels.some(m => !!m.picker_requires_pinning);
|
||||
panel.dataset.pickerMode = usesPinnedPicker ? 'pinned' : 'hidden';
|
||||
const warningHtml = warning ? `<div class="admin-error" style="font-size:11px;margin:6px 0;">${esc(warning)}</div>` : '';
|
||||
const attachRefresh = () => {
|
||||
panel.querySelector(`[data-ep-refresh-models="${epId}"]`)?.addEventListener('click', async (e) => {
|
||||
@@ -674,6 +684,7 @@ async function loadEndpoints() {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const refreshedModels = await res.json();
|
||||
renderModels(refreshedModels, refreshWarning);
|
||||
_refreshAfterEndpointChange();
|
||||
if (refreshWarning && uiModule?.showToast) uiModule.showToast(refreshWarning, 6000);
|
||||
} catch (_) {
|
||||
renderModels(sortedModels, 'Model refresh failed; kept cached models.');
|
||||
@@ -684,26 +695,26 @@ async function loadEndpoints() {
|
||||
panel.innerHTML = `<div class="mcp-tools-header">
|
||||
<span>Models</span>
|
||||
<span style="display:flex;gap:8px;align-items:center;">
|
||||
<span class="mcp-tools-count">0/0 enabled</span>
|
||||
<a href="#" data-ep-refresh-models="${epId}">Refresh</a>
|
||||
</span>
|
||||
</div>${warningHtml}<span style="opacity:0.5;font-size:11px;">No models</span>`;
|
||||
attachRefresh();
|
||||
return;
|
||||
}
|
||||
const hiddenSet = new Set(sortedModels.filter(m => m.is_hidden).map(m => m.id));
|
||||
const enabledCount = usesPinnedPicker
|
||||
? sortedModels.filter(m => m.is_pinned).length
|
||||
: sortedModels.filter(m => !m.is_hidden).length;
|
||||
const showSearch = sortedModels.length >= 8;
|
||||
panel.innerHTML = `<div class="mcp-tools-header">
|
||||
<span>Models</span>
|
||||
<span style="display:flex;gap:8px;align-items:center;">
|
||||
<span class="mcp-tools-count">${sortedModels.length - hiddenSet.size}/${sortedModels.length} enabled</span>
|
||||
<a href="#" data-ep-refresh-models="${epId}">Refresh</a>
|
||||
<a href="#" data-ep-select-all="${epId}">All</a>
|
||||
<a href="#" data-ep-select-none="${epId}">None</a>
|
||||
</span>
|
||||
</div>${warningHtml}${showSearch ? `<input type="search" class="mcp-tools-search" placeholder="Search ${sortedModels.length} models..." data-ep-search="${epId}">` : ''}<div class="mcp-tools-list">` + sortedModels.map(m =>
|
||||
`<label title="${esc(m.id)}" data-ep-model-row data-search="${esc((m.display + ' ' + m.id).toLowerCase())}" class="adm-model-row">
|
||||
<input type="checkbox" class="adm-cb-hidden" data-ep-model-id="${esc(m.id)}" ${!m.is_hidden ? 'checked' : ''}>
|
||||
<input type="checkbox" class="adm-cb-hidden" data-ep-model-id="${esc(m.id)}" ${(usesPinnedPicker ? m.is_pinned : !m.is_hidden) ? 'checked' : ''}>
|
||||
<span class="adm-check-dot" aria-hidden="true"></span>
|
||||
<span>${esc(m.display)}</span>
|
||||
</label>`
|
||||
@@ -744,35 +755,43 @@ async function loadEndpoints() {
|
||||
}
|
||||
});
|
||||
});
|
||||
refreshDependentModelUi();
|
||||
} catch (e) {
|
||||
const err = '<div class="admin-error">Failed to load</div>';
|
||||
[listLocal, listApi, listLegacy].forEach(c => { if (c) c.innerHTML = err; });
|
||||
[listLocal, listApi].forEach(c => { if (c) c.innerHTML = err; });
|
||||
}
|
||||
}
|
||||
|
||||
async function _saveEpModelState(epId, panel) {
|
||||
const hidden = [];
|
||||
const pinned = [];
|
||||
const usesPinnedPicker = panel && panel.dataset && panel.dataset.pickerMode === 'pinned';
|
||||
panel.querySelectorAll('input[type=checkbox]').forEach(cb => {
|
||||
if (!cb.checked) hidden.push(cb.dataset.epModelId);
|
||||
if (cb.checked) pinned.push(cb.dataset.epModelId);
|
||||
else hidden.push(cb.dataset.epModelId);
|
||||
});
|
||||
const total = panel.querySelectorAll('input[type=checkbox]').length;
|
||||
const enabled = usesPinnedPicker ? pinned.length : total - hidden.length;
|
||||
try {
|
||||
await fetch(`/api/model-endpoints/${epId}/models`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ hidden }),
|
||||
body: JSON.stringify(usesPinnedPicker ? { pinned_models: pinned } : { hidden }),
|
||||
});
|
||||
const countLabel = panel.querySelector('.mcp-tools-count');
|
||||
if (countLabel) countLabel.textContent = `${total - hidden.length}/${total} enabled`;
|
||||
const row = panel.closest('[data-adm-ep-id]');
|
||||
if (row) {
|
||||
const badge = row.querySelector('.admin-badge');
|
||||
if (badge && !badge.classList.contains('admin-badge-off')) badge.textContent = `${total - hidden.length}/${total} models enabled`;
|
||||
if (badge && !badge.classList.contains('admin-badge-off')) {
|
||||
const match = String(badge.textContent || '').match(/\/(\d+)/);
|
||||
const canonicalTotal = match ? Number(match[1]) : total;
|
||||
badge.textContent = `${enabled}/${canonicalTotal} models enabled`;
|
||||
}
|
||||
}
|
||||
if (settingsModule && typeof settingsModule.refreshAiModelEndpoints === 'function') {
|
||||
settingsModule.refreshAiModelEndpoints();
|
||||
}
|
||||
_refreshAfterEndpointChange();
|
||||
} catch (e) { /* silent */ }
|
||||
}
|
||||
|
||||
@@ -1480,6 +1499,7 @@ function initEndpointForm() {
|
||||
})());
|
||||
await Promise.all(workers);
|
||||
await loadEndpoints();
|
||||
await _refreshAfterEndpointChange();
|
||||
_refreshOfflineCount();
|
||||
if (uiModule && uiModule.showToast) {
|
||||
const ok = Math.max(0, ids.length - failed);
|
||||
@@ -1522,6 +1542,7 @@ function initEndpointForm() {
|
||||
await Promise.all(ids.map(id =>
|
||||
fetch('/api/model-endpoints/' + id, { method: 'DELETE', credentials: 'same-origin' }).catch(() => {})
|
||||
));
|
||||
await _refreshAfterEndpointChange();
|
||||
try { await loadEndpoints(); } catch (_) {}
|
||||
_refreshOfflineCount();
|
||||
if (uiModule && uiModule.showToast) uiModule.showToast(`Removed ${ids.length} offline endpoint${ids.length === 1 ? '' : 's'}`, 1800);
|
||||
|
||||
Reference in New Issue
Block a user