Squash Odysseus development history

This commit is contained in:
pewdiepie-archdaemon
2026-09-11 06:04:19 +00:00
parent e5c99a5eee
commit 6ee6502010
2050 changed files with 538359 additions and 57745 deletions
+50 -41
View File
@@ -5,16 +5,19 @@
*/
import Storage from './storage.js';
import uiModule from './ui.js';
import uiModule from './ui.js?v=20260908weekhoverfix1';
import sessionModule from './sessions.js';
import dragSortModule from './dragSort.js';
import spinnerModule from './spinner.js';
import { modelColor } from './chatRenderer.js';
import { modelColor } from './chatRenderer.js?v=20260910streamlinks2';
import { providerLogo } from './providers.js';
import { sortModelIds } from './modelSort.js';
let API_BASE = '';
let _cachedItems = []; // cached /api/models items for model-switch dropdown
let _lastFetchTime = 0;
let _fetchInflight = null;
let _fetchSeq = 0;
const _FETCH_CACHE_TTL = 30000; // 30s client-side cache for /api/models
const COLLAPSE_KEY = 'odysseus-models-collapsed';
const FAVORITES_KEY = 'odysseus-model-favorites';
@@ -161,32 +164,65 @@ function _buildModelRow(mid, url, displayName, endpointId, offline, modelType) {
return row;
}
export async function refreshModels(force = false) {
export async function refreshModels(force = false, opts = {}) {
const box = document.getElementById('models');
if (!box) return;
const cacheOnly = !!(opts && opts.cacheOnly);
const hasCache = _cachedItems.length > 0;
// Skip network fetch if cache is fresh and not forced — still re-render UI
// Cache-only is used for cheap picker/settings opens, but it must not turn a
// cold page load into an empty model list. If nothing has been fetched in this
// tab yet, do one normal load.
const now = Date.now();
const needsFetch = force || _cachedItems.length === 0 || (now - _lastFetchTime) >= _FETCH_CACHE_TTL;
const needsFetch = !(cacheOnly && hasCache) && (force || _cachedItems.length === 0 || (now - _lastFetchTime) >= _FETCH_CACHE_TTL);
box.innerHTML = '';
const hadRenderedRows = !!(box && box.children && box.children.length);
if (box && (!needsFetch || !hadRenderedRows)) box.innerHTML = '';
if (needsFetch) {
const _loadingSpinner = spinnerModule.create('', 'right', 'wave');
box.appendChild(_loadingSpinner.createElement());
_loadingSpinner.start();
let _loadingSpinner = null;
if (box) {
if (hadRenderedRows) {
box.classList.add('models-refreshing');
} else {
_loadingSpinner = spinnerModule.create('', 'right', 'wave');
box.appendChild(_loadingSpinner.createElement());
_loadingSpinner.start();
}
}
try {
const res = await fetch(`${API_BASE}/api/models`);
const data = await res.json();
if (force) _fetchInflight = null;
if (!_fetchInflight) {
// Pass ?refresh=true on forced refreshes so the BACKEND's 30s
// per-user cache also gets bypassed. Without this, `force=true`
// only clears the frontend cache and the same stale list comes
// back — newly-served endpoints don't appear until the cache
// ages out. (Bug repro: serve a model, picker is empty for ~30s
// even though the endpoint is in the DB and online.)
const _seq = ++_fetchSeq;
const _url = `${API_BASE}/api/models` + (force ? '?refresh=true' : '?background=false');
_fetchInflight = fetch(_url, { credentials: 'same-origin' })
.then(async (res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
return { data, seq: _seq };
})
.finally(() => { _fetchInflight = null; });
}
const { data, seq } = await _fetchInflight;
if (seq < _fetchSeq) return;
_lastFetchTime = Date.now();
_cachedItems = data.items || [];
} catch (e) {
console.error(e);
box.textContent = '(scan failed)';
if (box) box.textContent = '(scan failed)';
return;
} finally {
box.innerHTML = '';
try { _loadingSpinner && _loadingSpinner.stop && _loadingSpinner.stop(); } catch (_) {}
if (box) box.classList.remove('models-refreshing');
if (box) box.innerHTML = '';
}
}
if (!box) return;
try {
const collapseState = _loadCollapsed();
@@ -551,33 +587,6 @@ export async function refreshModels(force = false) {
+ '<span class="muted-sm">Ask an admin to configure model endpoints</span>';
}
box.appendChild(noModels);
// No endpoints yet: keep the welcome screen focused on first setup.
const welcomeSub = document.getElementById('welcome-sub');
if (welcomeSub) welcomeSub.innerHTML = 'Type <span style="color:var(--accent,var(--red));font-weight:600">/setup</span> to get started.';
const welcomeTip = document.getElementById('welcome-tip');
if (welcomeTip) welcomeTip.textContent = 'Type /setup, then choose Local models or API.';
} else {
// Configured installs should feel ready, not stuck in onboarding.
const welcomeSub = document.getElementById('welcome-sub');
if (welcomeSub) welcomeSub.textContent = 'Yours for the voyage.';
const welcomeTip = document.getElementById('welcome-tip');
if (welcomeTip) {
const tips = window.innerWidth <= 768
? [
'Tip: Long-press a session for rename, delete, and memory options.',
'Tip: Tap the eye icon for Nobody mode - no history saved.',
'Tip: Switch to Agent mode when you want tools.',
'Tip: Attach images or files using the + button next to the input.',
]
: [
'Tip: Press Ctrl+K to search across all your conversations.',
'Tip: Press Ctrl+B to quickly toggle the sidebar.',
'Tip: Shift-click the sidebar toggle to swap it to the other side.',
'Tip: Drag and drop files onto the chat to attach them.',
'Tip: Right-click a session for rename, delete, and memory options.',
];
welcomeTip.textContent = tips[Math.floor(Math.random() * tips.length)];
}
}
} catch (e) {
console.error(e);
@@ -603,7 +612,7 @@ export async function refreshProviders() {
if (openai) {
const models = (openai.items?.[0]?.models) || [];
models.forEach(m => {
sortModelIds(models).forEach(m => {
const opt = document.createElement('option');
opt.value = m;
opt.textContent = m;