diff --git a/ui_server.py b/ui_server.py index e1d8f8a..ad6a446 100644 --- a/ui_server.py +++ b/ui_server.py @@ -1039,6 +1039,15 @@ HTML = """ .netron-state { min-width: 0; color: #8e99a6; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .netron-panel { min-height: 420px; margin-top: 8px; overflow: hidden; border: 1px solid #2b3037; background: #fff; } .netron-frame { display: block; width: 100%; height: min(68vh, 720px); border: 0; background: #fff; } + .netron-tab { height: 100%; min-height: 0; overflow: auto; padding: 10px; } + .netron-page-panel { width: min(1400px, 100%) !important; min-height: 100%; margin: 0 auto; border: 1px solid var(--ui-border); background: var(--ui-surface); } + .netron-page-section { min-height: 100%; padding: 16px; border: 0; } + .netron-page-head { display: flex; align-items: end; justify-content: space-between; gap: 16px; flex-wrap: wrap; } + .netron-page-head h2 { margin: 0; } + .netron-model-field { min-width: min(420px, 100%); display: grid; gap: 4px; color: var(--ui-muted); font-size: 12px; } + .netron-model-field select { min-width: 0; height: 34px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-input); color: var(--ui-text); padding: 0 10px; font: inherit; } + .netron-page-section .netron-panel { min-height: calc(100vh - 230px); } + .netron-page-section .netron-frame { height: min(74vh, 860px); } .theme-select { width: 112px; height: 28px; border: 1px solid #2b3037; background: #101317; color: #dce3ea; padding: 0 5px; font: 11px Segoe UI, Arial, sans-serif; } .control-form { display: grid; grid-template-columns: 1fr 1fr; align-items: start; } .settings-group { min-width: 0; margin: 0; padding: 10px; border: 0; border-bottom: 1px solid #2b3037; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } @@ -1212,7 +1221,7 @@ HTML = """ .theme-select, .accent-select, .accent-custom { border-radius: 8px; transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease; } .theme-select:hover, .accent-select:hover, .accent-custom:hover { border-color: var(--ui-accent) !important; transform: translateY(-1px); } main { height: calc(100vh - 52px); } - .stream-tab, .archive-tab, .settings-tab, .model-tab { padding: 10px; } + .stream-tab, .archive-tab, .settings-tab, .model-tab, .netron-tab { padding: 10px; } .stage, .settings-panel, .model-panel, .archive-panel, .log-drawer, .player-box, .netron-panel { border-radius: var(--ui-radius); box-shadow: var(--ui-shadow); } .stage { isolation: isolate; } .stage::after { content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ui-accent) 8%, transparent); } @@ -1537,6 +1546,7 @@ HTML = """ +
@@ -1876,8 +1886,22 @@ HTML = """ Параметры применяются при следующем старте. -
-
Визуализация сети
+ + +
+ + @@ -3674,23 +3696,23 @@ HTML = """ } async function loadModels() { const data = await fetch('/api/models', {cache: 'no-store'}).then(r => r.json()); - const select = $('modelSelect'); - select.innerHTML = ''; - for (const model of data.models || []) { + const selects = [$('modelSelect'), $('netronModelSelect')].filter(Boolean); + selects.forEach(select => { select.innerHTML = ''; }); + for (const model of data.models || []) selects.forEach(select => { const option = document.createElement('option'); option.value = model.path; option.textContent = `${model.name} · ${mb(model.size)}`; select.appendChild(option); - } + }); const selected = data.selected || data.default || ''; - if (!select.options.length) { - select.innerHTML = ''; - } else if (selected) { - select.value = selected; - } - if (select.value) { - setValue('modelPath', select.value); - setText('modelState', `${select.selectedOptions[0]?.textContent || 'модель выбрана'}`); + selects.forEach(select => { + if (!select.options.length) select.innerHTML = ''; + else if (selected) select.value = selected; + }); + const modelSelect = $('modelSelect'); + if (modelSelect?.value) { + setValue('modelPath', modelSelect.value); + setText('modelState', `${modelSelect.selectedOptions[0]?.textContent || 'модель выбрана'}`); } } function resetNetron() { @@ -3699,7 +3721,7 @@ HTML = """ setText('netronState', 'граф Netron не загружен'); } async function openNetron() { - const path = $('modelSelect').value || $('modelPath').value; + const path = $('netronModelSelect')?.value || $('modelSelect').value || $('modelPath').value; if (!path) throw new Error('выберите файл модели .pt'); setText('netronState', 'запуск Netron...'); const response = await fetch('/api/model/netron?path=' + enc(path), {cache: 'no-store'}); @@ -4078,12 +4100,13 @@ HTML = """ $('playerOverlay').hidden = true; } function switchTab(name, updateHash = true) { - if (!['stream', 'archive', 'settings', 'model'].includes(name)) name = 'stream'; + if (!['stream', 'archive', 'settings', 'model', 'netron'].includes(name)) name = 'stream'; document.querySelectorAll('.tab').forEach(btn => btn.classList.toggle('active', btn.dataset.tab === name)); $('streamTab').hidden = name !== 'stream'; $('archiveTab').hidden = name !== 'archive'; $('settingsTab').hidden = name !== 'settings'; $('modelTab').hidden = name !== 'model'; + $('netronTab').hidden = name !== 'netron'; if (name === 'settings' && typeof applyStoredConfigLayout === 'function') requestAnimationFrame(applyStoredConfigLayout); if (name === 'model' && typeof applyNetworkLayout === 'function') requestAnimationFrame(applyNetworkLayout); if (name === 'archive') loadArchive(); @@ -4320,6 +4343,14 @@ HTML = """ $('decorIntensity').addEventListener('input', e => applyAppearanceSetting('decor', e.target.value)); document.querySelectorAll('[data-setting]').forEach(button => button.addEventListener('click', () => applyAppearanceSetting(button.dataset.setting, button.dataset.value))); $('modelSelect').addEventListener('change', e => { + setValue('modelPath', e.target.value); + setValue('netronModelSelect', e.target.value); + setText('modelState', e.target.selectedOptions[0]?.textContent || 'модель выбрана'); + resetNetron(); + controlDirty = true; + }); + $('netronModelSelect').addEventListener('change', e => { + setValue('modelSelect', e.target.value); setValue('modelPath', e.target.value); setText('modelState', e.target.selectedOptions[0]?.textContent || 'модель выбрана'); resetNetron();