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
+134 -49
View File
@@ -2,12 +2,12 @@
import state from './state.js';
import Storage from '../storage.js';
import { fetchModels, _persistSelections, getExcludedModels } from './models.js';
import { showScoreboard } from './scoreboard.js';
import { EYE_OPEN, EYE_CLOSED, ICON_DICE, ICON_PARALLEL, ICON_SEQUENTIAL, SAVE_ICON, WAVE_FRAMES, CHAT_ICON } from './icons.js';
import { showScoreboard } from './scoreboard.js?v=20260909voteconfirmalign1';
import { EYE_OPEN, EYE_CLOSED, ICON_DICE, ICON_PARALLEL, ICON_SEQUENTIAL, SAVE_ICON, WAVE_FRAMES, CHAT_ICON } from './icons.js?v=20260908compareprompts1';
import { _clearProbeWaves } from './probe.js';
import uiModule from '../ui.js';
import uiModule from '../ui.js?v=20260908weekhoverfix1';
import spinnerModule from '../spinner.js';
import themeModule from '../theme.js';
import themeModule from '../theme.js?v=20260909effectspeed1';
const escapeHtml = uiModule.esc;
@@ -75,7 +75,7 @@ async function showModelSelector() {
header.className = 'modal-header';
const title = document.createElement('h4');
title.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><path d="M11 18H8a2 2 0 0 1-2-2V9"/></svg>Model Comparison';
title.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><rect x="3" y="4" width="7" height="16" rx="1.5"/><rect x="14" y="4" width="7" height="16" rx="1.5"/><path d="M10 8h4"/><path d="M10 16h4"/></svg>Model Comparison';
// Absorb the free space so the injected minimize (_) and close (✕) cluster
// together on the right instead of being spread apart by space-between.
title.style.marginRight = 'auto';
@@ -153,7 +153,7 @@ async function showModelSelector() {
uiModule.showToast('Mode: ' + (state._parallel ? 'Parallel' : 'Sequential'));
_updateModeLabel();
_setModeHint(state._parallel
? '<span style="color:#5b8def">Parallel</span>: all models answer at once, side by side.'
? '<span style="color:var(--accent, var(--red))">Parallel</span>: all models answer at once, side by side.'
: '<span style="color:#e0a050">Sequential</span>: models answer one at a time.');
});
toggleRow.appendChild(parallelBtn);
@@ -295,7 +295,7 @@ async function showModelSelector() {
const parts = [];
if (state._blindMode) parts.push('<span style="color:var(--color-blind-orange)">Blind</span>');
parts.push(state._parallel
? '<span style="color:#5b8def">Parallel</span>'
? '<span style="color:var(--accent, var(--red))">Parallel</span>'
: '<span style="color:#e0a050">Sequential</span>');
if (_shuffled) parts.push('<span style="color:var(--red)">Shuffle</span>');
if (state._saveOnClose) parts.push('<span style="color:var(--color-save-green)">Save</span>');
@@ -403,6 +403,7 @@ async function showModelSelector() {
// Validate saved selections against available models (done after models load)
let _needsValidation = selections.length > 0;
let addBtn = null;
let startBtn = null;
let _shuffled = false;
_updateModeLabel(); // initial readout (Blind + Parallel on by default)
@@ -419,6 +420,68 @@ async function showModelSelector() {
};
}
function _selectionKey(sel) {
if (!sel) return '';
const provider = sel.searchProvider || '';
return [sel.model || '', sel.endpointId || '', sel.endpoint || '', provider].join('|');
}
function _duplicateSelectionKeys() {
const counts = new Map();
selections.filter(Boolean).forEach(sel => {
const key = _selectionKey(sel);
if (!key) return;
counts.set(key, (counts.get(key) || 0) + 1);
});
return new Set([...counts.entries()].filter(([, count]) => count > 1).map(([key]) => key));
}
function _appendSelectionMeta(row, sel, duplicateKeys) {
if (!sel || _shuffled) return;
const key = _selectionKey(sel);
const meta = document.createElement('div');
meta.className = 'cmp-model-meta';
const bits = [];
if (duplicateKeys.has(key)) {
row.classList.add('cmp-model-row-duplicate');
bits.push('<span class="cmp-model-meta-warning">Duplicate selection</span>');
}
if (!bits.length) return;
meta.innerHTML = bits.join('');
row.appendChild(meta);
}
function _updateStartReadiness() {
if (!startBtn || !_modelsLoaded) return;
const duplicates = _duplicateSelectionKeys();
// Duplicate models are valid compare inputs. Keep the warning on the
// rows, but never block the Start flow because the user can choose to
// proceed through the probe fallback.
startBtn.disabled = false;
startBtn.style.opacity = '1';
startBtn.title = duplicates.size > 0 ? 'Duplicate selections will run as separate panes' : '';
}
function _expandModelSlot(slotIdx) {
const row = listContainer.querySelector(`.cmp-model-row[data-slot-index="${slotIdx}"]`);
if (!row) return;
row.classList.add('cmp-model-row-swap-target');
row.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
const searchable = row.querySelector('.cmp-model-picker-input');
if (searchable) {
searchable.focus({ preventScroll: true });
return;
}
const select = row.querySelector('.cmp-model-primary-select');
if (!select) return;
select.focus({ preventScroll: true });
if (typeof select.showPicker === 'function') {
try { select.showPicker(); } catch (_) { /* focus still identifies the slot */ }
}
}
/** Build a searchable model picker (used when >5 models) */
function _buildSearchablePicker(modelList, currentSel, slotIdx, onSelect) {
const wrap = document.createElement('div');
@@ -427,7 +490,7 @@ async function showModelSelector() {
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Search models\u2026';
input.className = 'cmp-form-control';
input.className = 'cmp-form-control cmp-model-picker-input';
input.style.cssText = 'width:100%;box-sizing:border-box;';
// Mobile: suppress the on-screen keyboard so tapping the picker
// opens the dropdown but doesn't shove a keyboard up over the list.
@@ -594,6 +657,7 @@ async function showModelSelector() {
selections.forEach((sel, idx) => {
const row = document.createElement('div');
row.className = 'cmp-model-row';
row.dataset.slotIndex = String(idx);
if (_seqStepS) row.style.marginLeft = (idx * _seqStepS) + 'px';
// Left label: number/letter or blind eye icon
@@ -618,7 +682,7 @@ async function showModelSelector() {
row.appendChild(picker);
} else {
const modelSelect = document.createElement('select');
modelSelect.className = 'cmp-form-control';
modelSelect.className = 'cmp-form-control cmp-model-primary-select';
modelSelect.style.flex = '1';
chatModels.forEach(m => {
const opt = document.createElement('option');
@@ -647,26 +711,29 @@ async function showModelSelector() {
});
provSelect.addEventListener('change', () => {
try { selections[idx] = JSON.parse(provSelect.value); } catch (e) {}
renderModelRows();
});
try { if (!selections[idx]) selections[idx] = JSON.parse(provSelect.value); } catch (e) {}
row.appendChild(provSelect);
_appendSelectionMeta(row, selections[idx], _duplicateSelectionKeys());
// X remove button when >2 slots
if (selections.length > 2) {
// X remove button when more than one slot remains
if (selections.length > 1) {
const rmBtn = document.createElement('button');
rmBtn.type = 'button';
rmBtn.textContent = '\u00d7';
rmBtn.className = 'cmp-rm-btn';
rmBtn.addEventListener('mouseenter', () => { rmBtn.style.opacity = '1'; rmBtn.style.color = 'var(--color-error)'; });
rmBtn.addEventListener('mouseleave', () => { rmBtn.style.opacity = '0.3'; rmBtn.style.color = 'var(--fg)'; });
rmBtn.addEventListener('mouseenter', () => { rmBtn.style.opacity = '1'; rmBtn.style.color = 'var(--accent, var(--red))'; });
rmBtn.addEventListener('mouseleave', () => { rmBtn.style.opacity = '0.3'; rmBtn.style.color = 'var(--accent, var(--red))'; });
rmBtn.addEventListener('click', () => { selections.splice(idx, 1); state._searchSynthModels.splice(idx, 1); renderModelRows(); });
row.appendChild(rmBtn);
}
listContainer.appendChild(row);
});
if (addBtn) addBtn.style.display = selections.length >= 8 ? 'none' : '';
return;
listContainer.appendChild(row);
});
if (addBtn) addBtn.style.display = selections.length >= 8 ? 'none' : '';
_updateStartReadiness();
return;
}
// ── Chat / Image / Agent / Research mode: show model dropdowns ──
@@ -705,6 +772,7 @@ async function showModelSelector() {
selections.forEach((sel, idx) => {
const row = document.createElement('div');
row.className = 'cmp-model-row';
row.dataset.slotIndex = String(idx);
if (_seqStep) row.style.marginLeft = (idx * _seqStep) + 'px';
// Left label: number/letter or blind eye icon
@@ -727,6 +795,7 @@ async function showModelSelector() {
const picker = _buildSearchablePicker(filtered, sel, idx, (chosen) => {
selections[idx] = chosen;
_remindShuffle();
renderModelRows();
});
if (!selections[idx]) {
const fallback = filtered[Math.min(idx, filtered.length - 1)];
@@ -735,7 +804,7 @@ async function showModelSelector() {
row.appendChild(picker);
} else {
const select = document.createElement('select');
select.className = 'cmp-form-control';
select.className = 'cmp-form-control cmp-model-primary-select';
select.style.flex = '1';
filtered.forEach((m, mi) => {
const opt = buildOption(m);
@@ -749,6 +818,7 @@ async function showModelSelector() {
select.addEventListener('change', () => {
try { selections[idx] = JSON.parse(select.value); } catch (e) { console.warn('Compare model select parse failed:', e); }
_remindShuffle();
renderModelRows();
});
try { if (!selections[idx]) selections[idx] = JSON.parse(select.value); } catch (e) { console.warn('Compare model init parse failed:', e); }
row.appendChild(select);
@@ -767,19 +837,24 @@ async function showModelSelector() {
else if (!state._searchSynthModels[idx] && pi === 0) optEl.selected = true;
provSelect.appendChild(optEl);
});
provSelect.addEventListener('change', () => { state._searchSynthModels[idx] = provSelect.value; });
provSelect.addEventListener('change', () => {
state._searchSynthModels[idx] = provSelect.value;
renderModelRows();
});
if (!state._searchSynthModels[idx]) state._searchSynthModels[idx] = provSelect.value;
row.appendChild(provSelect);
}
// X remove button when >2 slots
if (selections.length > 2) {
_appendSelectionMeta(row, selections[idx], _duplicateSelectionKeys());
// X remove button when more than one slot remains
if (selections.length > 1) {
const rmBtn = document.createElement('button');
rmBtn.type = 'button';
rmBtn.textContent = '\u00d7';
rmBtn.className = 'cmp-rm-btn';
rmBtn.addEventListener('mouseenter', () => { rmBtn.style.opacity = '1'; rmBtn.style.color = 'var(--color-error)'; });
rmBtn.addEventListener('mouseleave', () => { rmBtn.style.opacity = '0.3'; rmBtn.style.color = 'var(--fg)'; });
rmBtn.addEventListener('mouseenter', () => { rmBtn.style.opacity = '1'; rmBtn.style.color = 'var(--accent, var(--red))'; });
rmBtn.addEventListener('mouseleave', () => { rmBtn.style.opacity = '0.3'; rmBtn.style.color = 'var(--accent, var(--red))'; });
rmBtn.addEventListener('click', () => { selections.splice(idx, 1); if (state._searchSynthModels.length > idx) state._searchSynthModels.splice(idx, 1); renderModelRows(); });
row.appendChild(rmBtn);
}
@@ -787,6 +862,7 @@ async function showModelSelector() {
listContainer.appendChild(row);
});
if (addBtn) addBtn.style.display = (selections.length >= 8) ? 'none' : '';
_updateStartReadiness();
}
// Default to 2 empty slots if no saved selections
@@ -795,7 +871,7 @@ async function showModelSelector() {
addBtn = document.createElement('button');
addBtn.type = 'button';
addBtn.style.cssText = 'display:none;align-items:center;gap:6px;background:none;border:1px dashed var(--border);color:var(--fg);border-radius:6px;cursor:pointer;padding:6px 12px;font-size:0.82em;opacity:0.6;transition:all 0.15s;margin-bottom:16px;width:100%;justify-content:center;';
addBtn.textContent = '+ Add Model';
addBtn.innerHTML = '<span style="color:var(--accent,var(--red));position:relative;left:-2px;">+</span><span>Add Model</span>';
addBtn.addEventListener('mouseenter', () => { addBtn.style.opacity = '1'; });
addBtn.addEventListener('mouseleave', () => { addBtn.style.opacity = '0.6'; });
addBtn.addEventListener('click', () => {
@@ -840,7 +916,7 @@ async function showModelSelector() {
// Scoreboard button
const scoreBtn = document.createElement('button');
scoreBtn.type = 'button';
scoreBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:4px;"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>Scoreboard';
scoreBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:0px;position:relative;top:2px;margin-right:4px;"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>Scoreboard';
scoreBtn.style.cssText = 'margin-left:auto;padding:4px 10px;background:transparent;color:var(--fg);border:1px solid var(--border);border-radius:4px;cursor:pointer;font-size:0.82em;opacity:0.7;position:relative;top:-5px;';
scoreBtn.addEventListener('mouseenter', () => { scoreBtn.style.opacity = '1'; });
scoreBtn.addEventListener('mouseleave', () => { scoreBtn.style.opacity = '0.7'; });
@@ -857,7 +933,7 @@ async function showModelSelector() {
footer.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;padding:14px 16px 10px;border-top:1px solid var(--border);';
// Cancel button removed — the overlay's X / outside-click / Esc all
// dismiss the popup, so the footer Cancel was redundant.
const startBtn = document.createElement('button');
startBtn = document.createElement('button');
startBtn.innerHTML = _CMP_START_LABEL;
startBtn.className = 'research-start-btn';
startBtn.disabled = true;
@@ -924,7 +1000,11 @@ async function showModelSelector() {
probeOverlay.className = 'compare-probe-overlay';
const probeCard = document.createElement('div');
probeCard.className = 'compare-probe-card';
probeCard.innerHTML = '<div class="compare-probe-title">Checking models...</div>';
probeCard.innerHTML = '<div class="compare-probe-title"><span class="compare-probe-title-label">Preround check:</span> <span class="compare-probe-title-status">Checking models...</span></div>';
const _setProbeTitle = (message) => {
const status = probeCard.querySelector('.compare-probe-title-status');
if (status) status.textContent = message;
};
let _probeSkipped = false;
const probeList = document.createElement('div');
probeList.className = 'compare-probe-list';
@@ -950,6 +1030,9 @@ async function showModelSelector() {
probeList.appendChild(row);
});
probeCard.appendChild(probeList);
const probeFeedback = document.createElement('div');
probeFeedback.className = 'compare-probe-feedback';
probeCard.appendChild(probeFeedback);
const skipBtn = document.createElement('button');
skipBtn.textContent = 'Skip';
skipBtn.className = 'cmp-btn-secondary';
@@ -1046,18 +1129,22 @@ async function showModelSelector() {
if (nameEl) nameEl.textContent = row._realName;
}
// Remove old detail/actions if retrying
const oldDetail = row.nextElementSibling;
if (oldDetail && oldDetail.classList.contains('compare-probe-detail')) oldDetail.remove();
// Error + actions below the row
const oldDetail = probeFeedback.querySelector(`[data-probe-detail="${idx}"]`);
if (oldDetail) oldDetail.remove();
// Keep diagnostic feedback below the complete model list.
const detail = document.createElement('div');
detail.className = 'compare-probe-detail';
detail.style.cssText = 'grid-column:1/-1;display:flex;align-items:flex-start;gap:6px;padding:4px 10px 6px;font-size:10px;opacity:0.6;background:color-mix(in srgb, var(--color-error, #f44) 5%, transparent);border-radius:4px;margin-top:-2px;';
detail.dataset.probeDetail = String(idx);
const detailIcon = document.createElement('span');
detailIcon.className = 'compare-probe-detail-icon';
const errSpan = document.createElement('span');
// Truncate long error messages
const errText = (result.error || 'Failed');
detailIcon.textContent = /insufficient balance/i.test(errText) ? '$' : '!';
detail.appendChild(detailIcon);
errSpan.textContent = errText.length > 80 ? errText.slice(0, 80) + '...' : errText;
errSpan.title = errText;
errSpan.style.cssText = 'flex:1;line-height:1.4;';
errSpan.className = 'compare-probe-detail-message';
detail.appendChild(errSpan);
// Track timeout for retry doubling
if (!row._probeTimeout) row._probeTimeout = 15000;
@@ -1065,7 +1152,7 @@ async function showModelSelector() {
const retryBtn = document.createElement('button');
retryBtn.className = 'compare-probe-action-btn';
const retryLabel = result.error === 'Timeout' ? `Retry ${Math.round(row._probeTimeout / 1000)}s` : 'Retry';
retryBtn.textContent = retryLabel;
retryBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11a8 8 0 1 0-2.34 5.66"/><polyline points="20 4 20 11 13 11"/></svg><span>' + escapeHtml(retryLabel) + '</span>';
retryBtn.addEventListener('click', async (e) => {
e.stopPropagation();
detail.remove();
@@ -1084,7 +1171,7 @@ async function showModelSelector() {
});
const swapBtn = document.createElement('button');
swapBtn.className = 'compare-probe-action-btn';
swapBtn.textContent = 'Swap';
swapBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m16 3 4 4-4 4"/><path d="M20 7H4"/><path d="m8 21-4-4 4-4"/><path d="M4 17h16"/></svg><span>Swap</span>';
swapBtn.addEventListener('click', (e) => {
e.stopPropagation();
_clearProbeWaves();
@@ -1093,10 +1180,12 @@ async function showModelSelector() {
startBtn.disabled = false;
startBtn.innerHTML = _CMP_START_LABEL;
startBtn.style.opacity = '1';
renderModelRows();
_expandModelSlot(idx);
});
detail.appendChild(retryBtn);
detail.appendChild(swapBtn);
row.after(detail);
probeFeedback.appendChild(detail);
}
}
@@ -1186,8 +1275,7 @@ async function showModelSelector() {
: (state._searchSynthModels || []).map(p => typeof p === 'string' ? { id: p, label: p } : null).filter(Boolean);
if (providers.length > 0) {
const titleEl = probeOverlay.querySelector('.compare-probe-title');
titleEl.textContent = 'Checking search providers...';
_setProbeTitle('Checking search providers...');
// Add provider rows
const providerRows = [];
@@ -1195,7 +1283,7 @@ async function showModelSelector() {
const row = document.createElement('div');
row.className = 'compare-probe-row';
row.dataset.idx = 'p' + i;
row.innerHTML = `<span class="compare-probe-spinner">▁▂▃</span><span class="compare-probe-name">${p.label || p.id}</span><span class="compare-probe-status"></span>`;
row.innerHTML = `<span class="compare-probe-spinner">▁▂▃</span><span class="compare-probe-name">${escapeHtml(p.label || p.id)}</span><span class="compare-probe-status"></span>`;
const waveEl = row.querySelector('.compare-probe-spinner');
const waveFrames = WAVE_FRAMES;
let wIdx = 0;
@@ -1250,7 +1338,7 @@ async function showModelSelector() {
// Don't hide the Skip button here — collapsing its space made the
// card shrink and the title + rows jump ("quick cut"). On success the
// whole overlay fades out a moment later, so just leave it in place.
probeOverlay.querySelector('.compare-probe-title').textContent = 'All ready!';
_setProbeTitle('All ready!');
setTimeout(() => {
probeOverlay.style.transition = 'opacity 0.3s ease';
probeOverlay.style.opacity = '0';
@@ -1264,21 +1352,18 @@ async function showModelSelector() {
probeList.querySelectorAll('.compare-probe-row.fail').forEach(row => {
failedNames.push(row.querySelector('.compare-probe-name').textContent);
});
const titleEl = probeOverlay.querySelector('.compare-probe-title');
titleEl.textContent = failedNames.length <= 2
_setProbeTitle(failedNames.length <= 2
? failedNames.join(' & ') + ' failed'
: `${failCount} models failed`;
: `${failCount} models failed`);
const btnRow = document.createElement('div');
btnRow.style.cssText = 'display:flex;gap:8px;justify-content:center;margin-top:12px;';
btnRow.className = 'compare-probe-footer';
const goBackBtn = document.createElement('button');
goBackBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><polyline points="15 18 9 12 15 6"/></svg>Go Back';
goBackBtn.className = 'cmp-btn-secondary';
goBackBtn.style.cssText = 'padding:5px 12px;font-size:12px;display:inline-flex;align-items:center;';
goBackBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg><span>Go Back</span>';
goBackBtn.className = 'cmp-btn-secondary compare-probe-footer-btn';
goBackBtn.addEventListener('click', () => { _clearProbeWaves(); probeOverlay.remove(); startBtn.disabled = false; startBtn.innerHTML = _CMP_START_LABEL; startBtn.style.opacity = '1'; });
const startAnywayBtn = document.createElement('button');
startAnywayBtn.textContent = 'Start Anyway';
startAnywayBtn.className = 'cmp-btn-primary';
startAnywayBtn.style.cssText = 'padding:5px 12px;font-size:12px;';
startAnywayBtn.innerHTML = _CMP_PLAY_ICON + '<span>Start Anyway</span>';
startAnywayBtn.className = 'cmp-btn-primary compare-probe-footer-btn compare-probe-start-anyway';
startAnywayBtn.addEventListener('click', () => { _clearProbeWaves(); probeOverlay.remove(); cleanup(true); });
btnRow.appendChild(goBackBtn);
btnRow.appendChild(startAnywayBtn);