// ============================================ // COOKBOOK MODULE (v2 — simplified) // What Fits? + Saved presets, inline action panels // ============================================ import uiModule from './ui.js?v=20260908weekhoverfix1'; import spinnerModule from './spinner.js'; import { providerLogo } from './providers.js'; import { makeWindowDraggable } from './windowDrag.js'; import { _diagnose, _showDiagnosis, _clearDiagnosis, _runQuickCmd, ERROR_PATTERNS } from './cookbook-diagnosis.js'; import { RECIPE_BACKENDS, recipesForBackend, pickRecipe, recipeCommands, RECIPE_DEFAULT_VARIANT } from './cookbook-deps-recipes.js'; import { _hwfitCache, _hwfitDebounce, _hwfitFetch, _hwfitInit, _hwfitRenderList, _hwfitRenderHw, _renderGpuToggles, _expandModelRow, _fitColors, _hwfitColumns, _cachedModelIds, _gpuToggleTotal, _resetGpuToggleState, _cancelHwfitRequests } from './cookbook-hwfit.js'; // Sub-modules import { initRunning, _loadTasks, _saveTasks, _addTask, _removeTask, _tmuxCmd, _renderRunningTab, _clearCookbookNotif, _launchServeTask, _serveAutoFix, _serveAutoRetry, _serveAutoRetryReplace, _serveAutoRetryRemove, _startBackgroundMonitor, _syncFromServer, _retryDownload, _nextAvailablePort, _processQueue, _selfHealStaleTasks, } from './cookbookRunning.js'; import { initDownload, _setPanelField, _setPanelCheckbox, _wirePanelEvents, _runPanelCmd, _runModelDownload, _buildDownloadCmd, } from './cookbookDownload.js'; import { initServe, _fetchCachedModels, _cachedAllModels, _filterCachedList, _rerenderCachedModels, _deleteCachedModel, _cancelCachedModelScan, } from './cookbookServe.js'; import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js'; import { topPortalZ } from './toolWindowZOrder.js'; const STORAGE_KEY = 'cookbook-presets'; const LAST_STATE_KEY = 'cookbook-last-state'; const SERVE_STATE_KEY = 'cookbook-serve-state'; let _dependenciesFetchId = 0; let _dependenciesRequestController = null; let _dependenciesModelHint = ''; function _fetchCookbookUiWithTimeout(input, init = {}, timeoutMs = 20000) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); return fetch(input, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer)); } // Radio-style check marking which model directory is a server's download target. // OFF = hollow circle (pickable); ON = checked circle (accent-tinted via CSS). export const _MODELDIR_CHECK_OFF = ''; export const _MODELDIR_CHECK_ON = ''; function _normalizeCookbookModelDir(dir) { const d = String(dir || '').replaceAll('✕', '').replaceAll('✖', '').trim(); return /^(home|mnt|media|data|opt|srv|var)\//.test(d) ? `/${d}` : d; } // Monochrome platform glyphs (currentColor) for a server's OS tag: a penguin for // Linux, the four-pane logo for Windows, an Android robot for Termux/Android. function _platformIcon(platform) { const k = (platform || '').toLowerCase(); if (k === 'windows') { return ''; } if (k === 'termux' || k === 'android') { return ''; } if (k === 'linux' || k === 'termux-linux') { return ''; } return ''; } export let _envState = { env: 'none', envPath: '', hfToken: '', hfTokenConfigured: false, hfTokenMasked: '', gpus: '', remoteHost: '', servers: [], modelPaths: [], platform: '', hostPlatform: '', defaultServer: '' }; let _lastCacheHostVal = null; let _cookbookOpeningSpinners = []; export function _lastCacheHost() { return _lastCacheHostVal; } export function _setLastCacheHost(v) { _lastCacheHostVal = v; } function _setCookbookOpening(on) { // Sidebar (tool-cookbook-btn) deliberately excluded — the inline // whirlpool on the sidebar row read as "the click didn't register" // rather than "loading", which made users (rightly) think clicks // were being eaten. Keep only the icon-rail spinner since the // rail is narrow enough that an obvious loading state still helps. const targets = [ document.getElementById('rail-cookbook'), ].filter(Boolean); if (!on) { _cookbookOpeningSpinners.forEach(({ spinner, wrap, target }) => { try { spinner?.stop?.(); } catch {} try { wrap?.remove?.(); } catch {} target?.classList?.remove('cookbook-opening'); }); _cookbookOpeningSpinners = []; return; } if (_cookbookOpeningSpinners.length) return; targets.forEach(target => { const spinner = spinnerModule.create('', 'clean', 'whirlpool'); spinner._wpSize = target.id === 'rail-cookbook' ? 12 : 13; const wrap = document.createElement('span'); wrap.className = 'cookbook-open-loading'; wrap.appendChild(spinner.createElement()); target.appendChild(wrap); target.classList.add('cookbook-opening'); spinner.start(); _cookbookOpeningSpinners.push({ spinner, wrap, target }); }); } /** Build server