mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 09:45:24 -04:00
Cookbook/Dependencies: populate recipe model picker from downloaded models
The recipe dropdown was a static catalog (MiniMax / Any vLLM model). Now it lists every model already downloaded on the active server (the same _cachedModelIds set the Launch tab + dl-dots already drive), plus an 'Other (generic …)' fallback. The change handler uses pickRecipe(backend, modelId) to find the best match — MiniMax ids land on the MiniMax recipe, everything else falls back to the generic install. cookbook-diagnosis.js: openCookbookDependencies's pre-select logic now matches by full option value (model id) instead of label substring, since the dropdown values are full repo ids now.
This commit is contained in:
@@ -101,22 +101,30 @@ function _openCookbookDependencies(pkgName = '', opts = {}) {
|
||||
row.classList.add('cookbook-pkg-flash');
|
||||
setTimeout(() => row.classList.remove('cookbook-pkg-flash'), 1800);
|
||||
// Pre-flight deep link: auto-expand the recipe panel + pre-select
|
||||
// the model the user was trying to launch.
|
||||
// the model the user was trying to launch. The dropdown values are
|
||||
// now full model ids (sourced from _cachedModelIds), so we match by
|
||||
// exact value first, then fall back to a substring match.
|
||||
if (opts.expandRecipe) {
|
||||
const caret = row.querySelector('[data-dep-recipe-toggle]');
|
||||
if (caret && caret.getAttribute('aria-expanded') !== 'true') caret.click();
|
||||
if (opts.model) {
|
||||
const sel = document.querySelector(`[data-dep-recipe-pick="${CSS.escape(opts.expandRecipe)}"]`);
|
||||
if (sel) {
|
||||
// Find first matching recipe; if none, leave on default.
|
||||
const wanted = String(opts.model);
|
||||
let matched = false;
|
||||
for (let i = 0; i < sel.options.length; i++) {
|
||||
const label = (sel.options[i].textContent || '').toLowerCase();
|
||||
if (/minimax/i.test(opts.model) && /minimax/i.test(label)) {
|
||||
sel.value = String(i);
|
||||
sel.dispatchEvent(new Event('change'));
|
||||
break;
|
||||
if (sel.options[i].value === wanted) {
|
||||
sel.value = wanted; matched = true; break;
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
for (let i = 0; i < sel.options.length; i++) {
|
||||
if (sel.options[i].value && wanted.includes(sel.options[i].value)) {
|
||||
sel.value = sel.options[i].value; matched = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matched) sel.dispatchEvent(new Event('change'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-5
@@ -836,11 +836,23 @@ async function _fetchDependencies() {
|
||||
|
||||
// Per-backend recipe panel (model picker + commands + Copy/Run).
|
||||
// Lives directly below the row it expands and starts collapsed.
|
||||
// The model picker lists every downloaded model from _cachedModelIds
|
||||
// (the same set the Launch tab uses); pickRecipe() then finds the
|
||||
// best-matching recipe for whatever the user selects, with the
|
||||
// backend's generic entry as the fallback.
|
||||
function _recipePanelHtml(backend) {
|
||||
const candidates = recipesForBackend(backend);
|
||||
if (!candidates.length) return '';
|
||||
const opts = candidates.map((r, i) => `<option value="${i}">${esc(r.label)}</option>`).join('');
|
||||
const initial = candidates[0];
|
||||
const downloadedIds = _cachedModelIds ? Array.from(_cachedModelIds).sort() : [];
|
||||
const modelOptions = downloadedIds.length
|
||||
? downloadedIds.map(id => `<option value="${esc(id)}">${esc(id)}</option>`).join('')
|
||||
: '';
|
||||
// "Other" entry: user types/pastes an id, OR uses the generic fallback
|
||||
// when no models have been downloaded yet.
|
||||
const otherOpt = `<option value="">Other (generic ${esc(backend)} install)</option>`;
|
||||
const opts = modelOptions + otherOpt;
|
||||
// Initial recipe: the generic fallback (matches first time, no model id).
|
||||
const initial = pickRecipe(backend, '') || candidates[0];
|
||||
return `<div class="cookbook-dep-recipe-panel" data-dep-recipe-panel="${esc(backend)}" style="display:none;margin:-4px 0 8px;padding:8px 12px 10px;background:rgba(0,0,0,0.04);border:1px solid var(--border);border-top:none;border-radius:0 0 6px 6px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
||||
<span style="font-size:11px;opacity:0.75;flex-shrink:0;">Serving which model?</span>
|
||||
@@ -974,12 +986,12 @@ async function _fetchDependencies() {
|
||||
if (caret) caret.style.transform = open ? 'rotate(180deg)' : '';
|
||||
});
|
||||
});
|
||||
// Model select: re-pick the matching recipe and refresh the <pre>.
|
||||
// Model select: pickRecipe matches the model id against the catalog
|
||||
// (e.g. minimax-m2.7 → MiniMax recipe; anything else → generic).
|
||||
list.querySelectorAll('[data-dep-recipe-pick]').forEach(sel => {
|
||||
sel.addEventListener('change', () => {
|
||||
const backend = sel.dataset.depRecipePick;
|
||||
const candidates = recipesForBackend(backend);
|
||||
const recipe = candidates[parseInt(sel.value, 10) || 0];
|
||||
const recipe = pickRecipe(backend, sel.value || '');
|
||||
if (!recipe) return;
|
||||
const pre = list.querySelector(`[data-dep-recipe-cmds="${CSS.escape(backend)}"]`);
|
||||
if (pre) pre.textContent = recipe.commands.join('\n');
|
||||
|
||||
Reference in New Issue
Block a user