refactor(ui): merge netron into model block

main
AlexsandrSnytkin 12 hours ago
parent d3ce82d399
commit eb41c0df28

@ -1039,15 +1039,7 @@ 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; } .network-model-netron { grid-column: 1 / -1; padding-top: 10px; border-top: 1px solid var(--ui-border); }
.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; }
@ -1221,7 +1213,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, .netron-tab { padding: 10px; } .stream-tab, .archive-tab, .settings-tab, .model-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); }
@ -1507,15 +1499,8 @@ HTML = """<!doctype html>
.network-block.settings-group input:not([type="checkbox"]):not([type="file"]), .network-block.settings-group select, .network-block.settings-group button { min-height: calc(28px * var(--block-scale)); height: calc(28px * var(--block-scale)); padding-left: calc(7px * var(--block-scale)); padding-right: calc(7px * var(--block-scale)); font-size: clamp(8px, calc(12px * var(--block-scale)), 26px); } .network-block.settings-group input:not([type="checkbox"]):not([type="file"]), .network-block.settings-group select, .network-block.settings-group button { min-height: calc(28px * var(--block-scale)); height: calc(28px * var(--block-scale)); padding-left: calc(7px * var(--block-scale)); padding-right: calc(7px * var(--block-scale)); font-size: clamp(8px, calc(12px * var(--block-scale)), 26px); }
.network-block.settings-group .network-summary { margin-bottom: calc(8px * var(--block-scale)); padding-bottom: calc(8px * var(--block-scale)); font-size: clamp(9px, calc(12px * var(--block-scale)), 24px); } .network-block.settings-group .network-summary { margin-bottom: calc(8px * var(--block-scale)); padding-bottom: calc(8px * var(--block-scale)); font-size: clamp(9px, calc(12px * var(--block-scale)), 24px); }
.network-block .model-actions { padding-top: 3px; } .network-block .model-actions { padding-top: 3px; }
.network-visual-card { padding: calc(14px * var(--block-scale)); } body[data-card-style="flat"] .network-block { box-shadow: none; border-radius: 8px !important; }
.network-visual-card .network-summary { margin-bottom: calc(8px * var(--block-scale)); padding-bottom: calc(8px * var(--block-scale)); font-size: clamp(9px, calc(12px * var(--block-scale)), 24px); } body[data-card-style="neon"] .network-block { border-color: color-mix(in srgb, var(--ui-accent) 48%, var(--ui-border)) !important; box-shadow: 0 0 0 1px color-mix(in srgb, var(--ui-accent) 12%, transparent), 0 10px 24px color-mix(in srgb, var(--ui-accent) 10%, transparent); }
.network-visual-card .netron-toolbar { gap: calc(8px * var(--block-scale)); margin-top: calc(10px * var(--block-scale)); }
.network-visual-card .netron-toolbar .command { height: calc(32px * var(--block-scale)); padding-left: calc(12px * var(--block-scale)); padding-right: calc(12px * var(--block-scale)); font-size: clamp(9px, calc(13px * var(--block-scale)), 24px); }
.network-visual-card .netron-state { font-size: clamp(8px, calc(12px * var(--block-scale)), 22px); }
.network-visual-card .netron-panel { min-height: max(180px, calc(420px * var(--block-scale))); margin-top: calc(8px * var(--block-scale)); }
.network-visual-card .netron-frame { height: min(68vh, calc(720px * var(--block-scale))); }
body[data-card-style="flat"] .network-block, body[data-card-style="flat"] .network-visual-card { box-shadow: none; border-radius: 8px !important; }
body[data-card-style="neon"] .network-block, body[data-card-style="neon"] .network-visual-card { border-color: color-mix(in srgb, var(--ui-accent) 48%, var(--ui-border)) !important; box-shadow: 0 0 0 1px color-mix(in srgb, var(--ui-accent) 12%, transparent), 0 10px 24px color-mix(in srgb, var(--ui-accent) 10%, transparent); }
@keyframes appearance-in { from { opacity: 0; transform: translateY(-5px) scale(.98); } to { opacity: 1; transform: none; } } @keyframes appearance-in { from { opacity: 0; transform: translateY(-5px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes preview-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } } @keyframes preview-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }
@keyframes preview-sheen { 0%, 55% { transform: translateX(0) skewX(-18deg); opacity: 0; } 70% { opacity: 1; } 100% { transform: translateX(430%) skewX(-18deg); opacity: 0; } } @keyframes preview-sheen { 0%, 55% { transform: translateX(0) skewX(-18deg); opacity: 0; } 70% { opacity: 1; } 100% { transform: translateX(430%) skewX(-18deg); opacity: 0; } }
@ -1531,7 +1516,6 @@ HTML = """<!doctype html>
.network-workspace.has-structure > .network-restore-panel { border-right: 0; border-bottom: 1px solid var(--ui-border); max-height: 42vh; } .network-workspace.has-structure > .network-restore-panel { border-right: 0; border-bottom: 1px solid var(--ui-border); max-height: 42vh; }
.network-canvas { min-height: 900px; padding: 10px; } .network-canvas { min-height: 900px; padding: 10px; }
.network-block { min-width: 220px; max-width: calc(100% - 8px); } .network-block { min-width: 220px; max-width: calc(100% - 8px); }
.network-visual-card { padding: 10px; }
} }
@media (max-width: 420px) { .appearance-options { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 420px) { .appearance-options { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
</style> </style>
@ -1546,7 +1530,6 @@ 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">
@ -1869,6 +1852,16 @@ HTML = """<!doctype html>
<span id="modelState" class="model-state">модель не выбрана</span> <span id="modelState" class="model-state">модель не выбрана</span>
</div> </div>
<label class="model-wide">Путь<input id="modelPath" type="text" readonly></label> <label class="model-wide">Путь<input id="modelPath" type="text" readonly></label>
<div class="netron-section network-model-netron">
<div class="network-summary">Визуализация сети</div>
<div class="netron-toolbar">
<button id="modelNetron" class="command primary" type="button">Показать в Netron</button>
<span id="netronState" class="netron-state">граф Netron не загружен</span>
</div>
<div id="netronPanel" class="netron-panel" hidden>
<iframe id="netronFrame" class="netron-frame" title="Визуализация архитектуры сети в Netron" loading="lazy" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
</div>
</section> </section>
<section class="model-group settings-group network-block" data-section="model-params"> <section class="model-group settings-group network-block" data-section="model-params">
<div class="network-summary">Параметры до инференса</div> <div class="network-summary">Параметры до инференса</div>
@ -1890,28 +1883,6 @@ HTML = """<!doctype html>
</div> </div>
</section> </section>
</div> </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">
<button id="modelNetron" class="command primary" type="button">Показать в Netron</button>
<span id="netronState" class="netron-state">граф Netron не загружен</span>
</div>
<div id="netronPanel" class="netron-panel" hidden>
<iframe id="netronFrame" class="netron-frame" title="Визуализация архитектуры сети в Netron" loading="lazy" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
</div>
</section>
</div>
<div id="archiveTab" class="tab-page archive-tab" hidden> <div id="archiveTab" class="tab-page archive-tab" hidden>
<section class="archive-panel"> <section class="archive-panel">
<h2>&#1040;&#1088;&#1093;&#1080;&#1074;</h2> <h2>&#1040;&#1088;&#1093;&#1080;&#1074;</h2>
@ -3696,23 +3667,20 @@ 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 selects = [$('modelSelect'), $('netronModelSelect')].filter(Boolean); const select = $('modelSelect');
selects.forEach(select => { select.innerHTML = ''; }); select.innerHTML = '';
for (const model of data.models || []) selects.forEach(select => { for (const model of data.models || []) {
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 || '';
selects.forEach(select => { if (!select.options.length) select.innerHTML = '<option value="">модели .pt не найдены</option>';
if (!select.options.length) select.innerHTML = '<option value="">модели .pt не найдены</option>'; else if (selected) select.value = selected;
else if (selected) select.value = selected; if (select.value) {
}); setValue('modelPath', select.value);
const modelSelect = $('modelSelect'); setText('modelState', `${select.selectedOptions[0]?.textContent || 'модель выбрана'}`);
if (modelSelect?.value) {
setValue('modelPath', modelSelect.value);
setText('modelState', `${modelSelect.selectedOptions[0]?.textContent || 'модель выбрана'}`);
} }
} }
function resetNetron() { function resetNetron() {
@ -3721,7 +3689,7 @@ HTML = """<!doctype html>
setText('netronState', 'граф Netron не загружен'); setText('netronState', 'граф Netron не загружен');
} }
async function openNetron() { async function openNetron() {
const path = $('netronModelSelect')?.value || $('modelSelect').value || $('modelPath').value; const path = $('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'});
@ -4100,13 +4068,12 @@ HTML = """<!doctype html>
$('playerOverlay').hidden = true; $('playerOverlay').hidden = true;
} }
function switchTab(name, updateHash = true) { function switchTab(name, updateHash = true) {
if (!['stream', 'archive', 'settings', 'model', 'netron'].includes(name)) name = 'stream'; if (!['stream', 'archive', 'settings', 'model'].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();
@ -4343,14 +4310,6 @@ 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