qwen2
ExocoreV1 / templates /train.html
ChoruYt's picture
ExocoreV1: multi-thinking, web search, auto-load, loss chart, architecture tree, no system prompt
5e7ce6a verified
Raw
History Blame Contribute Delete
20.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ExocoreV1 — Train</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', sans-serif; background: #0d0d0f; color: #e0e0e0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
header { background: #111116; border-bottom: 1px solid #222; padding: 10px 20px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
header h1 { font-size: 1.1rem; color: #a78bfa; font-weight: 700; letter-spacing: 1px; }
header nav a { color: #888; text-decoration: none; margin-left: 14px; font-size: 0.85rem; }
header nav a:hover { color: #a78bfa; }
.layout { display: flex; flex: 1; overflow: hidden; }
.left { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.right { width: 360px; border-left: 1px solid #1e1e2a; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.card { background: #111116; border: 1px solid #1e1e2a; border-radius: 10px; padding: 14px; }
.card h3 { font-size: 0.82rem; color: #c4b5fd; font-weight: 600; margin-bottom: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
.tabs { display: flex; gap: 4px; margin-bottom: 14px; }
.tab { background: #16161f; border: 1px solid #252535; color: #666; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 0.8rem; }
.tab.active { background: #1a1628; border-color: #a78bfa; color: #a78bfa; }
.fr { display: flex; flex-direction: column; gap: 3px; margin-bottom: 9px; }
.fr label { font-size: 0.7rem; color: #555; }
.fr input, .fr textarea, .fr select { background: #16161f; border: 1px solid #252535; color: #ddd; border-radius: 5px; padding: 7px 10px; font-size: 0.82rem; width: 100%; font-family: inherit; }
.fr input:focus, .fr textarea:focus { outline: none; border-color: #7c3aed; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.btn { border: none; border-radius: 6px; padding: 8px 16px; cursor: pointer; font-size: 0.82rem; font-weight: 500; }
.btn-p { background: #7c3aed; color: white; }
.btn-p:hover { background: #6d28d9; }
.btn-p:disabled { background: #252535; color: #555; cursor: not-allowed; }
.btn-d { background: #4a1010; color: #fca5a5; }
.btn-d:hover { background: #5f1414; }
.btn-s { background: #16161f; border: 1px solid #252535; color: #888; }
.btn-s:hover { border-color: #a78bfa; color: #a78bfa; }
.btn-sm { padding: 4px 10px; font-size: 0.75rem; }
.progress-track { background: #16161f; border-radius: 20px; height: 6px; margin: 7px 0; overflow: hidden; }
.progress-fill { background: linear-gradient(90deg, #7c3aed, #a78bfa); height: 100%; border-radius: 20px; transition: width 0.3s; }
.log-box { background: #080810; border: 1px solid #1a1a24; border-radius: 5px; padding: 8px; font-family: monospace; font-size: 0.73rem; color: #666; max-height: 130px; overflow-y: auto; white-space: pre-wrap; }
.stat-row { display: flex; align-items: center; gap: 7px; font-size: 0.78rem; color: #666; margin-bottom: 7px; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: #333; flex-shrink: 0; }
.dot.running { background: #22c55e; animation: pulse 1s infinite; }
.dot.done { background: #22c55e; }
.dot.error { background: #ef4444; }
@keyframes pulse { 0%,100%{opacity:1}50%{opacity:.4} }
.mcard { background: #16161f; border: 1px solid #252535; border-radius: 7px; padding: 9px 12px; margin-bottom: 6px; display: flex; align-items: center; justify-content: space-between; }
.mcard .mn { font-size: 0.83rem; color: #ddd; font-weight: 500; }
.mcard .mt { font-size: 0.7rem; color: #555; margin-top: 2px; }
.pb { font-size: 0.68rem; padding: 2px 7px; border-radius: 20px; background: #1a1628; border: 1px solid #4c1d95; color: #a78bfa; margin-left: 5px; }
.chart-wrap { position: relative; height: 220px; margin-top: 8px; }
.arch-tree { font-size: 0.78rem; }
.arch-node { margin: 3px 0; display: flex; align-items: center; gap: 6px; }
.arch-node .label { background: #16161f; border: 1px solid #252535; border-radius: 5px; padding: 4px 10px; font-size: 0.75rem; color: #c4b5fd; flex: 1; }
.arch-node.block .label { border-color: #4c1d95; background: #1a1628; }
.arch-node.sub .label { border-color: #1e3a5f; background: #111820; color: #93c5fd; margin-left: 20px; font-size: 0.72rem; }
.arch-node.head-node .label { border-color: #134e2a; background: #0d1f16; color: #86efac; }
.arch-line { color: #333; font-size: 0.65rem; margin-left: 8px; }
::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar-thumb { background: #2a2a3a; border-radius: 3px; }
</style>
</head>
<body>
<header>
<h1>⬡ ExocoreV1</h1>
<nav><a href="/">Chat</a><a href="/train">Train</a></nav>
</header>
<div class="layout">
<!-- LEFT PANEL -->
<div class="left">
<div class="tabs">
<div class="tab active" onclick="showTab('train')">Train</div>
<div class="tab" onclick="showTab('auto')">Auto-Train (Gemini)</div>
</div>
<!-- TRAIN TAB -->
<div id="tab-train">
<div class="card">
<h3>Training Data</h3>
<div class="fr">
<label>Training Text</label>
<textarea id="trainText" rows="7" placeholder="Paste training text here (min 100 chars)…"></textarea>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<button class="btn btn-s btn-sm" onclick="loadTrainTxt()">Load train.txt</button>
<span id="charCount" style="font-size:0.73rem;color:#555;"></span>
</div>
</div>
<div class="card">
<h3>Model Configuration</h3>
<div class="fr"><label>Model Name</label><input type="text" id="modelName" value="Exocore"></div>
<div class="grid2">
<div class="fr"><label>Epochs</label><input type="number" id="epochs" value="60" min="1"></div>
<div class="fr"><label>Embedding Dim</label><input type="number" id="nEmbd" value="384" min="64"></div>
<div class="fr"><label>Layers</label><input type="number" id="nLayer" value="6" min="1"></div>
<div class="fr"><label>Block Size</label><input type="number" id="blockSize" value="256" min="32"></div>
<div class="fr"><label>Batch Size</label><input type="number" id="batchSize" value="16" min="1"></div>
<div class="fr"><label>Learning Rate</label><input type="number" id="lr" value="0.0002" step="0.00001"></div>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-p" id="trainBtn" onclick="startTraining()">Start Training</button>
<button class="btn btn-d" id="stopBtn" onclick="stopTraining()" style="display:none;">Stop</button>
</div>
</div>
<div class="card" id="trainStatusCard" style="display:none;">
<h3>Training Progress</h3>
<div class="stat-row">
<span class="dot" id="tDot"></span>
<span id="tStatus">Idle</span>
<span id="tEpoch" style="margin-left:auto;color:#555;font-size:0.73rem;"></span>
<span id="tLoss" style="color:#a78bfa;font-size:0.73rem;margin-left:8px;"></span>
</div>
<div class="progress-track"><div class="progress-fill" id="tProg" style="width:0%"></div></div>
<div class="chart-wrap"><canvas id="lossChart"></canvas></div>
<div class="log-box" id="tLog"></div>
</div>
</div>
<!-- AUTO TRAIN TAB -->
<div id="tab-auto" style="display:none;">
<div class="card">
<h3>Gemini Auto-Train</h3>
<p style="font-size:0.78rem;color:#555;margin-bottom:10px;">Gemini generates training data automatically, then ExocoreV1 trains on it.</p>
<div class="fr"><label>Initial Prompt</label><textarea id="autoPrompt" rows="3" placeholder="e.g. Explain quantum computing in detail."></textarea></div>
<div class="fr"><label>Continue Message</label><input type="text" id="autoContinue" value="ok next train"></div>
<div class="fr"><label>Model Name</label><input type="text" id="autoModel" value="Exocore"></div>
<div style="display:flex;gap:8px;">
<button class="btn btn-p" id="autoStartBtn" onclick="startAuto()">Start</button>
<button class="btn btn-d" id="autoStopBtn" onclick="stopAuto()" style="display:none;">Stop</button>
</div>
</div>
<div class="card" id="autoStatusCard" style="display:none;">
<h3>Auto-Train Status</h3>
<div class="stat-row"><span class="dot" id="aDot"></span><span id="aStatus">Idle</span></div>
<div class="log-box" id="aLog"></div>
</div>
</div>
</div>
<!-- RIGHT PANEL -->
<div class="right">
<!-- ARCHITECTURE TREE -->
<div class="card">
<h3>ExocoreV1 Architecture</h3>
<div class="arch-tree">
<div class="arch-node"><div class="label" style="text-align:center;color:#f59e0b;border-color:#78350f;background:#1c1208;">Input Tokens</div></div>
<div style="text-align:center;color:#333;font-size:0.7rem;"></div>
<div class="arch-node"><div class="label">Token Embedding <span style="color:#555;">151,936 → 1024</span></div></div>
<div style="text-align:center;color:#333;font-size:0.7rem;">↓ ×28 Transformer Blocks</div>
<div class="arch-node block">
<div class="label">Transformer Block (×28)</div>
</div>
<div class="arch-node sub"><div class="label">Input LayerNorm <span style="color:#555;">[1024]</span></div></div>
<div class="arch-node sub"><div class="label">GQA Attention <span style="color:#555;">16Q / 8KV heads · dim 128</span></div></div>
<div class="arch-node sub" style="margin-left:38px;"><div class="label" style="font-size:0.68rem;color:#6b7280;">Q proj 1024→2048 · K,V 1024→1024 · O 2048→1024</div></div>
<div class="arch-node sub" style="margin-left:38px;"><div class="label" style="font-size:0.68rem;color:#6b7280;">Q-Norm & K-Norm (RMSNorm per head)</div></div>
<div class="arch-node sub" style="margin-left:38px;"><div class="label" style="font-size:0.68rem;color:#6b7280;">RoPE · θ=1,000,000 · max 40,960 tokens</div></div>
<div class="arch-node sub"><div class="label">Post-Attn LayerNorm <span style="color:#555;">[1024]</span></div></div>
<div class="arch-node sub"><div class="label">SwiGLU FFN <span style="color:#555;">1024→3072→1024</span></div></div>
<div style="text-align:center;color:#333;font-size:0.7rem;"></div>
<div class="arch-node"><div class="label">Final RMSNorm <span style="color:#555;">[1024]</span></div></div>
<div style="text-align:center;color:#333;font-size:0.7rem;"></div>
<div class="arch-node head-node"><div class="label">Language Model Head <span style="color:#555;">1024 → 151,936</span></div></div>
<div style="text-align:center;color:#333;font-size:0.7rem;"></div>
<div class="arch-node"><div class="label" style="text-align:center;color:#f59e0b;border-color:#78350f;background:#1c1208;">Output Tokens</div></div>
<div style="margin-top:12px;padding-top:10px;border-top:1px solid #1e1e2a;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;">
<div style="background:#16161f;border:1px solid #252535;border-radius:5px;padding:5px 8px;"><div style="font-size:0.82rem;color:#c4b5fd;font-weight:600;">751.6M</div><div style="font-size:0.65rem;color:#555;">Parameters</div></div>
<div style="background:#16161f;border:1px solid #252535;border-radius:5px;padding:5px 8px;"><div style="font-size:0.82rem;color:#c4b5fd;font-weight:600;">28</div><div style="font-size:0.65rem;color:#555;">Layers</div></div>
<div style="background:#16161f;border:1px solid #252535;border-radius:5px;padding:5px 8px;"><div style="font-size:0.82rem;color:#c4b5fd;font-weight:600;">bfloat16</div><div style="font-size:0.65rem;color:#555;">Precision</div></div>
<div style="background:#16161f;border:1px solid #252535;border-radius:5px;padding:5px 8px;"><div style="font-size:0.82rem;color:#c4b5fd;font-weight:600;">40,960</div><div style="font-size:0.65rem;color:#555;">Max Context</div></div>
</div>
</div>
</div>
</div>
<!-- TRAINED MODELS -->
<div class="card">
<h3>Trained Models</h3>
<div id="modelList"><p style="color:#444;font-size:0.78rem;">Loading…</p></div>
<button class="btn btn-s btn-sm" onclick="loadModels()" style="margin-top:8px;">Refresh</button>
</div>
</div>
</div>
<script>
let trainInterval = null;
let autoInterval = null;
let lossChart = null;
function showTab(name) {
['train','auto'].forEach(t => {
document.getElementById('tab-' + t).style.display = t === name ? '' : 'none';
});
document.querySelectorAll('.tab').forEach((el, i) => {
el.classList.toggle('active', ['train','auto'][i] === name);
});
}
async function loadTrainTxt() {
const r = await fetch('/api/train/text');
const d = await r.json();
if (d.found) {
document.getElementById('trainText').value = d.text;
document.getElementById('charCount').textContent = d.chars.toLocaleString() + ' chars';
} else { alert('train.txt not found.'); }
}
document.getElementById('trainText').addEventListener('input', function() {
document.getElementById('charCount').textContent = this.value.length.toLocaleString() + ' chars';
});
function initChart() {
const ctx = document.getElementById('lossChart').getContext('2d');
lossChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Training Loss',
data: [],
borderColor: '#a78bfa',
backgroundColor: 'rgba(167,139,250,0.08)',
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: '#a78bfa',
tension: 0.3,
fill: true,
}]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#888', font: { size: 11 } } } },
scales: {
x: { grid: { color: '#1a1a24' }, ticks: { color: '#555', font: { size: 10 } }, title: { display: true, text: 'Epoch', color: '#555', font: { size: 10 } } },
y: { grid: { color: '#1a1a24' }, ticks: { color: '#555', font: { size: 10 } }, title: { display: true, text: 'Loss', color: '#555', font: { size: 10 } } },
},
animation: { duration: 200 }
}
});
}
function updateChart(history) {
if (!lossChart) initChart();
lossChart.data.labels = history.map(h => 'E' + h.epoch);
lossChart.data.datasets[0].data = history.map(h => h.loss);
lossChart.update('none');
}
async function startTraining() {
const text = document.getElementById('trainText').value;
if (text.length < 100) { alert('Need at least 100 characters of training text.'); return; }
const config = {
epochs: parseInt(document.getElementById('epochs').value),
n_embd: parseInt(document.getElementById('nEmbd').value),
n_layer: parseInt(document.getElementById('nLayer').value),
block_size: parseInt(document.getElementById('blockSize').value),
batch_size: parseInt(document.getElementById('batchSize').value),
lr: parseFloat(document.getElementById('lr').value),
};
const model_name = document.getElementById('modelName').value || 'Exocore';
const r = await fetch('/api/train/start', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ text, config, model_name })
});
const d = await r.json();
if (d.error) { alert(d.error); return; }
document.getElementById('trainStatusCard').style.display = '';
document.getElementById('trainBtn').disabled = true;
document.getElementById('stopBtn').style.display = '';
if (!lossChart) initChart();
lossChart.data.labels = [];
lossChart.data.datasets[0].data = [];
lossChart.update('none');
trainInterval = setInterval(pollTrain, 1500);
}
async function stopTraining() {
await fetch('/api/train/stop', { method: 'POST' });
}
async function pollTrain() {
const r = await fetch('/api/train/status');
const d = await r.json();
const dot = document.getElementById('tDot');
document.getElementById('tStatus').textContent = d.status || (d.running ? 'Training…' : (d.error ? 'Error' : 'Done'));
dot.className = 'dot ' + (d.running ? 'running' : (d.error ? 'error' : 'done'));
const pct = d.total_epochs > 0 ? (d.epoch / d.total_epochs * 100) : 0;
document.getElementById('tProg').style.width = pct + '%';
document.getElementById('tEpoch').textContent = d.total_epochs ? `Epoch ${d.epoch}/${d.total_epochs}` : '';
document.getElementById('tLoss').textContent = d.loss ? `Loss: ${d.loss}` : '';
if (d.loss_history && d.loss_history.length) updateChart(d.loss_history);
if (d.log && d.log.length) {
const lb = document.getElementById('tLog');
lb.textContent = d.log.slice(-30).join('\n');
lb.scrollTop = lb.scrollHeight;
}
if (!d.running) {
clearInterval(trainInterval);
document.getElementById('trainBtn').disabled = false;
document.getElementById('stopBtn').style.display = 'none';
if (d.loss_history) updateChart(d.loss_history);
loadModels();
}
}
async function startAuto() {
const prompt = document.getElementById('autoPrompt').value.trim();
if (!prompt) { alert('Enter an initial prompt.'); return; }
const r = await fetch('/api/gemini/autotrain/start', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({
initial_prompt: prompt,
continue_msg: document.getElementById('autoContinue').value,
model_name: document.getElementById('autoModel').value,
config: {}
})
});
const d = await r.json();
if (d.error) { alert(d.error); return; }
document.getElementById('autoStatusCard').style.display = '';
document.getElementById('autoStartBtn').disabled = true;
document.getElementById('autoStopBtn').style.display = '';
autoInterval = setInterval(pollAuto, 2000);
}
async function stopAuto() {
await fetch('/api/gemini/autotrain/stop', { method: 'POST' });
}
async function pollAuto() {
const r = await fetch('/api/gemini/autotrain/status');
const d = await r.json();
document.getElementById('aDot').className = 'dot ' + (d.running ? 'running' : (d.error ? 'error' : 'done'));
document.getElementById('aStatus').textContent = d.status || (d.running ? 'Running…' : 'Done');
if (d.log && d.log.length) {
const lb = document.getElementById('aLog');
lb.textContent = d.log.slice(-20).join('\n');
lb.scrollTop = lb.scrollHeight;
}
if (!d.running) {
clearInterval(autoInterval);
document.getElementById('autoStartBtn').disabled = false;
document.getElementById('autoStopBtn').style.display = 'none';
loadModels();
}
}
const PROTECTED = ['exocore.pt'];
async function loadModels() {
const r = await fetch('/api/train/models');
const d = await r.json();
const list = document.getElementById('modelList');
if (!d.models || !d.models.length) {
list.innerHTML = '<p style="color:#444;font-size:0.78rem;">No trained models yet.</p>';
return;
}
list.innerHTML = '';
d.models.forEach(m => {
const name = typeof m === 'string' ? m : (m.name || m);
const isProt = PROTECTED.includes(name.toLowerCase());
const div = document.createElement('div');
div.className = 'mcard';
div.innerHTML = `
<div>
<div class="mn">${name}${isProt ? '<span class="pb">Protected</span>' : ''}</div>
<div class="mt">ExocoreV1 model</div>
</div>
<div style="display:flex;gap:5px;">
<button class="btn btn-s btn-sm" onclick="viewInfo('${name}')">Info</button>
${isProt ? '' : `<button class="btn btn-d btn-sm" onclick="deleteModel('${name}')">Delete</button>`}
</div>`;
list.appendChild(div);
});
}
async function viewInfo(name) {
const r = await fetch('/api/train/model-info/' + encodeURIComponent(name));
const d = await r.json();
if (d.error) { alert('Error: ' + d.error); return; }
const lines = Object.entries(d).map(([k,v]) => `${k}: ${JSON.stringify(v)}`).join('\n');
alert(lines);
}
async function deleteModel(name) {
if (!confirm(`Delete "${name}"?`)) return;
const r = await fetch('/api/model/delete', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ local_name: name })
});
const d = await r.json();
if (d.error) { alert('Error: ' + d.error); }
else { loadModels(); }
}
loadModels();
</script>
</body>
</html>