feat(ui): move netron to its own tab

main
AlexsandrSnytkin 13 hours ago
parent d6016ccf6d
commit d3ce82d399

@ -1039,6 +1039,15 @@ HTML = """<!doctype html>
.netron-state { min-width: 0; color: #8e99a6; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .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-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-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; } .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; } .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; } .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 = """<!doctype html>
.theme-select, .accent-select, .accent-custom { border-radius: 8px; transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease; } .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); } .theme-select:hover, .accent-select:hover, .accent-custom:hover { border-color: var(--ui-accent) !important; transform: translateY(-1px); }
main { height: calc(100vh - 52px); } 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, .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 { 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); } .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 = """<!doctype html>
<button class="tab" type="button" data-tab="archive">Архив</button> <button class="tab" type="button" data-tab="archive">Архив</button>
<button class="tab" type="button" data-tab="settings">Настройки</button> <button class="tab" type="button" data-tab="settings">Настройки</button>
<button class="tab" type="button" data-tab="model">Сеть</button> <button class="tab" type="button" data-tab="model">Сеть</button>
<button class="tab" type="button" data-tab="netron">Netron</button>
</nav> </nav>
<div class="appearance-module"> <div class="appearance-module">
<button id="appearanceToggle" class="appearance-trigger" type="button" aria-expanded="false" aria-controls="appearancePanel"> <button id="appearanceToggle" class="appearance-trigger" type="button" aria-expanded="false" aria-controls="appearancePanel">
@ -1840,7 +1850,7 @@ HTML = """<!doctype html>
<aside id="networkRestorePanel" class="config-restore-panel network-restore-panel" hidden> <aside id="networkRestorePanel" class="config-restore-panel network-restore-panel" hidden>
<span class="config-restore-title">Структура сети</span> <span class="config-restore-title">Структура сети</span>
<div class="config-structure-create"> <div class="config-structure-create">
<label>Шаблон<select id="networkTemplateSelect"><option value="model">Модель</option><option value="model-params">Параметры</option><option value="netron">Netron</option></select></label> <label>Шаблон<select id="networkTemplateSelect"><option value="model">Модель</option><option value="model-params">Параметры</option></select></label>
<label>Название<input id="networkBlockTitle" type="text" maxlength="48" placeholder="Мой блок"></label> <label>Название<input id="networkBlockTitle" type="text" maxlength="48" placeholder="Мой блок"></label>
<button id="networkCreateBlock" type="button">создать блок</button> <button id="networkCreateBlock" type="button">создать блок</button>
</div> </div>
@ -1876,8 +1886,22 @@ HTML = """<!doctype html>
<label>Макс. детекций<input id="modelMaxDet" type="number" min="1" max="300" step="1" value="60"></label> <label>Макс. детекций<input id="modelMaxDet" type="number" min="1" max="300" step="1" value="60"></label>
<span class="model-state model-wide">Параметры применяются при следующем старте.</span> <span class="model-state model-wide">Параметры применяются при следующем старте.</span>
</section> </section>
<section class="netron-section network-visual-card network-block" data-section="netron"> </div>
<div class="network-summary">Визуализация сети</div> </div>
</section>
</div>
<div id="netronTab" class="tab-page netron-tab" hidden>
<section class="model-panel netron-page-panel">
<div class="netron-section netron-page-section">
<div class="netron-page-head">
<div>
<h2>Netron</h2>
<span class="netron-state">Архитектура модели до инференса</span>
</div>
<label class="netron-model-field">Модель
<select id="netronModelSelect"><option value="">загрузка списка...</option></select>
</label>
</div>
<div class="netron-toolbar"> <div class="netron-toolbar">
<button id="modelNetron" class="command primary" type="button">Показать в Netron</button> <button id="modelNetron" class="command primary" type="button">Показать в Netron</button>
<span id="netronState" class="netron-state">граф Netron не загружен</span> <span id="netronState" class="netron-state">граф Netron не загружен</span>
@ -1885,8 +1909,6 @@ HTML = """<!doctype html>
<div id="netronPanel" class="netron-panel" hidden> <div id="netronPanel" class="netron-panel" hidden>
<iframe id="netronFrame" class="netron-frame" title="Визуализация архитектуры сети в Netron" loading="lazy" sandbox="allow-scripts allow-same-origin"></iframe> <iframe id="netronFrame" class="netron-frame" title="Визуализация архитектуры сети в Netron" loading="lazy" sandbox="allow-scripts allow-same-origin"></iframe>
</div> </div>
</section>
</div>
</div> </div>
</section> </section>
</div> </div>
@ -3674,23 +3696,23 @@ HTML = """<!doctype html>
} }
async function loadModels() { async function loadModels() {
const data = await fetch('/api/models', {cache: 'no-store'}).then(r => r.json()); const data = await fetch('/api/models', {cache: 'no-store'}).then(r => r.json());
const select = $('modelSelect'); const selects = [$('modelSelect'), $('netronModelSelect')].filter(Boolean);
select.innerHTML = ''; selects.forEach(select => { select.innerHTML = ''; });
for (const model of data.models || []) { for (const model of data.models || []) selects.forEach(select => {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = model.path; option.value = model.path;
option.textContent = `${model.name} · ${mb(model.size)}`; option.textContent = `${model.name} · ${mb(model.size)}`;
select.appendChild(option); select.appendChild(option);
} });
const selected = data.selected || data.default || ''; const selected = data.selected || data.default || '';
if (!select.options.length) { selects.forEach(select => {
select.innerHTML = '<option value="">модели .pt не найдены</option>'; if (!select.options.length) select.innerHTML = '<option value="">модели .pt не найдены</option>';
} else if (selected) { else if (selected) select.value = selected;
select.value = selected; });
} const modelSelect = $('modelSelect');
if (select.value) { if (modelSelect?.value) {
setValue('modelPath', select.value); setValue('modelPath', modelSelect.value);
setText('modelState', `${select.selectedOptions[0]?.textContent || 'модель выбрана'}`); setText('modelState', `${modelSelect.selectedOptions[0]?.textContent || 'модель выбрана'}`);
} }
} }
function resetNetron() { function resetNetron() {
@ -3699,7 +3721,7 @@ HTML = """<!doctype html>
setText('netronState', 'граф Netron не загружен'); setText('netronState', 'граф Netron не загружен');
} }
async function openNetron() { async function openNetron() {
const path = $('modelSelect').value || $('modelPath').value; const path = $('netronModelSelect')?.value || $('modelSelect').value || $('modelPath').value;
if (!path) throw new Error('выберите файл модели .pt'); if (!path) throw new Error('выберите файл модели .pt');
setText('netronState', 'запуск Netron...'); setText('netronState', 'запуск Netron...');
const response = await fetch('/api/model/netron?path=' + enc(path), {cache: 'no-store'}); const response = await fetch('/api/model/netron?path=' + enc(path), {cache: 'no-store'});
@ -4078,12 +4100,13 @@ HTML = """<!doctype html>
$('playerOverlay').hidden = true; $('playerOverlay').hidden = true;
} }
function switchTab(name, updateHash = 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)); document.querySelectorAll('.tab').forEach(btn => btn.classList.toggle('active', btn.dataset.tab === name));
$('streamTab').hidden = name !== 'stream'; $('streamTab').hidden = name !== 'stream';
$('archiveTab').hidden = name !== 'archive'; $('archiveTab').hidden = name !== 'archive';
$('settingsTab').hidden = name !== 'settings'; $('settingsTab').hidden = name !== 'settings';
$('modelTab').hidden = name !== 'model'; $('modelTab').hidden = name !== 'model';
$('netronTab').hidden = name !== 'netron';
if (name === 'settings' && typeof applyStoredConfigLayout === 'function') requestAnimationFrame(applyStoredConfigLayout); if (name === 'settings' && typeof applyStoredConfigLayout === 'function') requestAnimationFrame(applyStoredConfigLayout);
if (name === 'model' && typeof applyNetworkLayout === 'function') requestAnimationFrame(applyNetworkLayout); if (name === 'model' && typeof applyNetworkLayout === 'function') requestAnimationFrame(applyNetworkLayout);
if (name === 'archive') loadArchive(); if (name === 'archive') loadArchive();
@ -4320,6 +4343,14 @@ HTML = """<!doctype html>
$('decorIntensity').addEventListener('input', e => applyAppearanceSetting('decor', e.target.value)); $('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))); document.querySelectorAll('[data-setting]').forEach(button => button.addEventListener('click', () => applyAppearanceSetting(button.dataset.setting, button.dataset.value)));
$('modelSelect').addEventListener('change', e => { $('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); setValue('modelPath', e.target.value);
setText('modelState', e.target.selectedOptions[0]?.textContent || 'модель выбрана'); setText('modelState', e.target.selectedOptions[0]?.textContent || 'модель выбрана');
resetNetron(); resetNetron();

Loading…
Cancel
Save