Spaces:
Running
Running
File size: 10,377 Bytes
bb59211 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | /** ๆจกๅๆจ็่งๅพ โโ ้ๆจกๅ/ๆฃๆฅ็นใ็ๅฎ็่ฎญ็ปๅๆฐใ้ๆต่ฏ้,็ถๅ่ท่ฏไผฐใ
*
* ไธ `static/js/train/` ๅๆ:ไธไธชๅ
ฅๅฃ + ่ฅๅนฒๆจกๅ,ๅ
ฑ็จ `static/js/shared/` ไธ็
* ็บฏๅทฅๅ
ทใ**ไธๆฐๅผไธๅฅ** โโ ๆจ็ๅ่ฎญ็ปๆฌๆฅๅฐฑๆฏๅไธๆนไบง็ฉ็ไธไธๆธธใ
*
* **ๅ็ซฏๆง่กๆชๆฅ** โโ ใ่ฟ่กๆจ็ใๆ้ฎๅ่ฎญ็ป้กตไธๆ ทๆฏ**ๆ
ๆ็ฆ็จ**็,ๆ่พนๅๆๅๅ ใ
* ่ฟไธ้กต็ๆญฃ่ฝ็จ็ๆฏๅ้ขไธๆฎต(้ๆจกๅ / ็ๅๆฐ / ้ๆต่ฏ้),ๆฐๆฎ้ฝๆฏ็็:
* ๆจกๅไธๆฃๆฅ็น โ GET /api/v1/training/runs
* ่ฎญ็ปๅๆฐ โ ้ฃไบ checkpoint ้็ train_config.json
* ๆต่ฏ้ โ GET /api/v1/training/datasets
*
* ๆฒกๆใๅฎๆถๆฅๅฟใใๆๆ ๆฒ็บฟใไน็ฑป็็ฉบ้กต็ญพ โโ ๆฒกๆๆฐๆฎ็้กต็ญพไธๆ,็็ฑๅ่ฎญ็ป้กตใ
*
* โ
ๆฐๆฎๆบไธบไปไน็ดๆฅๅผ `../train/api.js`:runs / datasets ่ฟไธคไธช็ซฏ็นๅฑไบ**่ฎญ็ปๅ**
* (ๆจ็ๆถ่ดน็ๆฏ่ฎญ็ปไบง็ฉ)ใ็ญๅฐๆฅๆ็ฌฌไธไธช้กต้ขไน็จๅฐๅฎไปฌ,ๅๆฝๅฐ shared/ ๅปใ
*/
import { el, clear } from '../shared/dom.js';
import { tr } from '../shared/text.js';
import { listDatasets, listRuns } from '../train/api.js';
const INPUT_CLASS = 'bg-gray-800 text-xs rounded px-2 py-1 border border-gray-700 '
+ 'focus:border-blue-500 focus:outline-none font-mono';
const BTN = 'bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-2.5 py-1 '
+ 'rounded border border-gray-700';
const LIVE = 'bg-green-900/50 text-green-300';
const PENDING = 'bg-amber-900/50 text-amber-300';
function section(titleKey, fallback, badge, children) {
return el('div', { class: 'bg-gray-900 rounded-lg border border-gray-800 p-4 mb-4' }, [
el('div', { class: 'flex items-center gap-3 mb-3' }, [
el('div', { class: 'text-sm font-semibold text-gray-200', text: tr(titleKey, fallback) }),
badge ? el('span', { class: `text-[10px] px-1.5 py-0.5 rounded ${badge.cls}`, text: badge.text }) : null,
]),
...children,
]);
}
function labeled(label, control, labelClass = 'text-gray-500 shrink-0 w-32') {
return el('div', { class: 'flex items-center gap-3 text-xs' }, [
el('span', { class: labelClass, text: label }),
control,
]);
}
function hint(key, fallback) {
return el('div', { class: 'text-xs text-gray-600 mt-3', text: tr(key, fallback) });
}
/** ไธ่กใๅๆฐๅ โ ๅผใใๆจ็ๅๆ่ฏฅ็กฎ่ฎค็ๅฐฑๆฏ**่ฎญ็ปๆถ็จ็ chunk_size /
* n_action_steps** ่ฟ็ฑปไผ็ดๆฅๅฝฑๅๆจ็่พๅบ็ๅๆฐ,ๆไปฅ่ฟ้ๅชๆ่ฟๅ ไธชๅ
ณ้ฎ็,
* ๅฎๆด็่ฎญ็ปๅๆฐๅจ่ฎญ็ป้กต้ฃไธชใ่ฎญ็ปๅๆฐใ้กต็ญพ้ๆใ */
function paramRow(name, value) {
return el('div', { class: 'flex items-baseline gap-3 text-xs py-0.5' }, [
el('span', { class: 'text-gray-500 shrink-0 w-32 font-mono', text: name }),
el('span', { class: 'text-gray-200 font-mono break-all', text: value ?? 'โ' }),
]);
}
const KEY_PARAMS = [
'policy.type', 'policy.chunk_size', 'policy.n_action_steps',
'policy.dim_model', 'policy.vision_backbone',
];
function pick(obj, path) {
return path.split('.').reduce((a, k) => (a === null || a === undefined ? undefined : a[k]), obj);
}
/**
* @param {HTMLElement} container
* @param {{runName?: string, checkpointName?: string}} [preselect]
* ไปใๆจกๅ่ฎญ็ปใ้กต็ญพ็นๆไธชๆฃๆฅ็น็ใๆจ็ใ่ทณ่ฟๆฅๆถๅธฆไธ โโ ็ดๆฅ้ไธญๅฎ,
* ไธ็จๅๆๅจๅจไธๆ้ๆพไธ้ใ
*/
export function renderInference(container, preselect) {
clear(container);
const state = { run: null, checkpoint: null, dataset: null };
// โโ ้ๆฉๆจกๅ / ๆฃๆฅ็น โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const runSelect = el('select', { class: INPUT_CLASS + ' w-64' });
const stepSelect = el('select', { class: INPUT_CLASS + ' w-40', disabled: true });
const paramHost = el('div', { class: 'mt-3' });
let runs = [];
function renderCheckpointParams() {
clear(paramHost);
const config = state.checkpoint?.train_config;
if (!config || !Object.keys(config).length) {
paramHost.appendChild(el('div', {
class: 'text-xs text-gray-600 py-2',
text: tr('inf_no_params', 'This checkpoint has no train_config.json; parameters unavailable.'),
}));
return;
}
paramHost.appendChild(el('div', {
class: 'text-[10px] text-gray-600 mb-2',
text: `${tr('inf_params_from', 'From')} checkpoints/${state.checkpoint.name}/pretrained_model/train_config.json`,
}));
paramHost.appendChild(el('div', { class: 'border border-gray-800 rounded p-3' },
KEY_PARAMS.map((p) => paramRow(p, pick(config, p)))));
}
function fillSteps(wantedStep) {
clear(stepSelect);
const run = runs.find((r) => r.name === runSelect.value);
if (!run) {
stepSelect.disabled = true;
stepSelect.appendChild(el('option', { value: '', text: 'โ' }));
state.checkpoint = null;
renderCheckpointParams();
return;
}
stepSelect.disabled = false;
for (const c of run.checkpoints) {
stepSelect.appendChild(el('option', { value: c.name, text: `${c.name} (${c.step})` }));
}
// ๆ้ข้ๅฐฑ็จ้ข้,ๅฆๅ็จๆๆฐ็้ฃไธชใ้ข้ๅๅฏ่ฝๅฏนไธไธ(ๆฏๅฆ้ฃไธช checkpoint
// ๅ่ขซๅ ),ๅฏนไธไธๅฐฑ้ๅๆๆฐ โโ ไธ่ฆๅ ไธบไธไธช่ฟๆ็ๅๆฐ่ฎฉๆด้กต็ฉบ็ใ
const pick = (wantedStep && run.checkpoints.find((c) => c.name === wantedStep))
|| run.checkpoints[0] || null;
state.checkpoint = pick;
if (pick) stepSelect.value = pick.name;
renderCheckpointParams();
}
runSelect.addEventListener('change', () => {
state.run = runSelect.value || null;
fillSteps();
});
stepSelect.addEventListener('change', () => {
const run = runs.find((r) => r.name === runSelect.value);
state.checkpoint = (run?.checkpoints || []).find((c) => c.name === stepSelect.value) || null;
renderCheckpointParams();
});
const sectionModel = section('inf_sec_model', '1. Model', null, [
el('div', { class: 'flex gap-6 flex-wrap' }, [
labeled(tr('inf_label_run', 'Run'), runSelect),
labeled(tr('inf_label_step', 'Checkpoint'), stepSelect),
]),
paramHost,
]);
paramHost.appendChild(el('div', { class: 'text-xs text-gray-600 py-2', text: tr('loading', 'Loading...') }));
listRuns()
.then((payload) => {
runs = payload.runs || [];
clear(runSelect);
if (!runs.length) {
runSelect.appendChild(el('option', { value: '', text: tr('inf_no_models', 'No trained model yet') }));
renderCheckpointParams();
return;
}
for (const r of runs) {
runSelect.appendChild(el('option', { value: r.name, text: `${r.name} (${r.checkpoint_count})` }));
}
const chosen = runs.find((r) => r.name === preselect?.runName) || runs[0];
runSelect.value = chosen.name;
state.run = chosen.name;
fillSteps(preselect?.checkpointName);
})
.catch((err) => {
clear(paramHost);
paramHost.appendChild(el('div', {
class: 'text-xs text-red-400 py-2',
text: `${tr('train_load_failed', 'Failed to load')}: ${err.message}`,
}));
});
// โโ ๆต่ฏ้ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const datasetSelect = el('select', { class: INPUT_CLASS + ' w-80' }, [
el('option', { value: '', text: tr('inf_dataset_none', 'โ Select a test dataset โ') }),
]);
datasetSelect.addEventListener('change', () => {
const value = datasetSelect.value;
state.dataset = value ? { name: value } : null;
});
const datasetNote = el('div', { class: 'text-xs text-gray-600 mt-2' });
const sectionData = section('inf_sec_data', '2. Test Data',
{ cls: LIVE, text: tr('inf_live', 'live') }, [
labeled(tr('inf_label_dataset', 'Test dataset'), datasetSelect),
datasetNote,
]);
datasetNote.textContent = tr('loading', 'Loading...');
listDatasets()
.then((payload) => {
const items = payload.datasets || [];
datasetNote.textContent = items.length
? `${items.length} ${tr('inf_datasets_available', 'dataset(s) available')}`
: tr('inf_no_datasets', 'No exported dataset yet โ export one from the review page first.');
for (const d of items) {
datasetSelect.appendChild(el('option', {
value: d.name,
text: `${d.name} โ ${d.total_episodes} ${tr('train_episodes', 'episodes')}`,
}));
}
})
.catch(() => { datasetNote.textContent = ''; });
// โโ ่ฟ่ก โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const sectionRun = section('inf_sec_run', '3. Run', { cls: PENDING, text: tr('inf_pending', 'not wired') }, [
el('div', { class: 'flex items-center gap-3' }, [
el('button', {
class: 'bg-blue-600/40 text-blue-200/60 text-sm px-5 py-2 rounded cursor-not-allowed',
text: tr('inf_run_btn', 'Run inference'),
disabled: true,
title: tr('inf_run_disabled', 'Backend execution is not wired up yet'),
}),
el('span', {
class: 'text-xs text-amber-400',
text: tr('inf_run_note', 'Backend execution is not connected yet โ this button is intentionally disabled.'),
}),
]),
hint('inf_run_hint', 'Once wired, this runs the checkpoint over the chosen test set and reports direction cosine, amplitude ratio, gripper MAE and the error curve.'),
]);
// โโ ็ปๆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const sectionResult = section('inf_sec_result', '4. Results', null, [
el('div', { class: 'text-center py-8' }, [
el('div', { class: 'text-xs text-gray-600', text: tr('inf_no_result', 'No evaluation result yet.') }),
]),
]);
for (const node of [sectionModel, sectionData, sectionRun, sectionResult]) {
container.appendChild(node);
}
}
// ่ฟ้**ๆฒกๆ** boot/่ชๅฏๅจ โโ ๆฌๆจกๅไธๅๆฏ้กต้ขๅ
ฅๅฃ,่ๆฏ่ขซ train/main.js
// ๅฝไฝ็ฌฌไธไธช้กต็ญพ import ่ฟๅป(`render: renderInference`)ใ
// ้กต็ญพ็ render ็ญพๅๆฏ (panel, run),ๆฌๅฝๆฐๅช็จๅฐ panel,ๅคๆถไธไธชๅๆฐๆ ๅฆจใ
|