Files
aipackage/cezen-portal/dataset-creation.html
T

390 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dataset Creation — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=12">
<style>
/* ── Shared layout ────────────────────────────────────────── */
.tr-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:28px; margin-bottom:28px; }
.tr-card-title { font-size:16px; font-weight:700; color:var(--ink); margin-bottom:4px; }
.tr-card-sub { font-size:13px; color:var(--lt); margin-bottom:20px; }
.tr-btn { padding:9px 18px; border-radius:8px; font-size:13px; font-weight:600; cursor:pointer; border:none; transition:all .15s; font-family:inherit; }
.tr-btn.primary { background:var(--purple); color:var(--ink); }
.tr-btn.primary:hover { background:#6D28D9; }
.tr-btn.ghost { background:rgba(255,255,255,.03); color:var(--med); border:1px solid var(--bdr); }
.tr-btn.ghost:hover { border-color:var(--purple); color:var(--purple); }
.tr-btn.danger { background:rgba(185,28,28,.08); color:#B91C1C; border:1px solid rgba(239,68,68,.25); }
.tr-btn.danger:hover { background:#DC2626; color:var(--ink); }
.tr-btn:disabled { opacity:.45; cursor:not-allowed; }
.tr-table-wrap { border:1px solid var(--bdr); border-radius:10px; overflow:hidden; }
table.tr-table { width:100%; border-collapse:collapse; }
.tr-table th { background:rgba(255,255,255,.03); padding:10px 14px; text-align:left; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:var(--lt); border-bottom:1px solid var(--bdr); }
.tr-table td { padding:12px 14px; border-bottom:1px solid var(--bdr); font-size:13px; color:var(--ink); vertical-align:middle; }
.tr-table tr:last-child td { border-bottom:none; }
.tr-table tr:hover td { background:rgba(255,255,255,.03); }
.tr-empty { text-align:center; color:var(--lt); padding:40px 0; font-size:13px; }
/* ── Dataset upload zone ──────────────────────────────────── */
.upload-zone { border:2px dashed var(--bdr); border-radius:10px; padding:32px; text-align:center; cursor:pointer; transition:.2s; background:rgba(255,255,255,.03); }
.upload-zone:hover, .upload-zone.drag { border-color:var(--purple); background:rgba(124,58,237,.12); }
.upload-zone input { display:none; }
.upload-zone .uz-icon { font-size:32px; margin-bottom:8px; }
.upload-zone .uz-label { font-size:14px; font-weight:600; color:var(--ink); margin-bottom:4px; }
.upload-zone .uz-hint { font-size:12px; color:var(--lt); }
.upload-progress { margin-top:10px; font-size:13px; color:var(--purple); display:none; }
/* ── Launch form ──────────────────────────────────────────── */
.launch-form { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
@media(max-width:640px){ .launch-form { grid-template-columns:1fr; } }
.launch-form .full { grid-column:1/-1; }
.lf-label { display:block; font-size:12px; font-weight:600; color:var(--med); margin-bottom:5px; }
.lf-input, .lf-select { width:100%; padding:9px 12px; border:1.5px solid var(--bdr); border-radius:8px; font-size:13px; font-family:inherit; outline:none; color:var(--ink); box-sizing:border-box; }
.lf-input:focus, .lf-select:focus { border-color:var(--purple); }
.lf-hint { font-size:11px; color:var(--lt); margin-top:3px; }
.param-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:14px; }
@media(max-width:640px){ .param-grid { grid-template-columns:1fr 1fr; } }
/* ── Status badges ────────────────────────────────────────── */
.badge-status { display:inline-block; font-size:10px; font-weight:700; padding:2px 9px; border-radius:10px; text-transform:uppercase; letter-spacing:.4px; }
.badge-status.running { background:rgba(34,197,94,.15); color:#15803D; }
.badge-status.completed { background:#DBEAFE; color:#1D4ED8; }
.badge-status.failed { background:#FEE2E2; color:#B91C1C; }
.badge-status.pending { background:rgba(234,179,8,.15); color:#92400E; }
.badge-status.cancelled { background:#F3F4F6; color:#6B7280; }
.badge-status.approved { background:rgba(34,197,94,.15); color:#15803D; }
.badge-status.rejected { background:#FEE2E2; color:#B91C1C; }
/* ── Log viewer ───────────────────────────────────────────── */
.log-wrap { background:#0F1117; border-radius:8px; padding:14px 16px; font-family:monospace; font-size:11px; line-height:1.6; color:#A3E635; max-height:220px; overflow-y:auto; margin-top:14px; display:none; white-space:pre-wrap; word-break:break-all; }
.log-toggle { font-size:12px; color:var(--purple); cursor:pointer; font-weight:600; margin-top:8px; display:inline-block; }
/* ── Loss chart ───────────────────────────────────────────── */
.chart-wrap { margin-top:16px; display:none; }
.chart-wrap canvas { max-width:100%; border-radius:8px; }
/* ── Job row expanded ─────────────────────────────────────── */
.job-detail { padding:14px 20px 18px; background:#F8FAFC; border-top:1px solid var(--bdr); display:none; }
.job-detail.open { display:block; }
.job-meta { display:flex; gap:24px; flex-wrap:wrap; font-size:12px; color:var(--lt); margin-bottom:10px; }
.job-meta span { display:flex; gap:4px; }
.job-meta strong { color:var(--ink); }
/* ── Toast ────────────────────────────────────────────────── */
#tr-toast { position:fixed; bottom:24px; right:24px; background:#1E3A5F; color:var(--ink); padding:12px 20px; border-radius:10px; font-size:13px; display:none; z-index:9999; box-shadow:0 8px 24px rgba(0,0,0,.2); max-width:340px; }
#tr-toast.err { background:#7F1D1D; }
/* ── Toolbar ──────────────────────────────────────────────── */
.tr-toolbar { display:flex; align-items:center; gap:10px; margin-bottom:16px; flex-wrap:wrap; }
.creator-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; }
.creator-grid .full { grid-column:1/-1; }
.creator-actions { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.sample-card { border:1px solid var(--bdr); border-radius:10px; padding:16px; margin-top:12px; background:rgba(255,255,255,.02); }
.sample-head { display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:12px; }
.sample-fields { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
.sample-fields textarea { min-height:110px; resize:vertical; }
.source-preview { margin-top:10px; font-size:11px; color:var(--lt); max-height:70px; overflow:auto; white-space:pre-wrap; }
@media(max-width:720px){ .creator-grid,.sample-fields { grid-template-columns:1fr; } }
</style>
</head>
<body data-role="admin">
<header class="topnav">
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
<nav>
<a href="index.html">Home</a>
<a href="quickstart.html">Quick Start</a>
<a href="prompts.html">Prompt Library</a>
<a href="usecases.html">Use Cases</a>
<span class="nav-sep"></span>
<div class="nav-dropdown">
<button class="nav-drop-btn">Help ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">LEARN /</span>
<a href="quickstart.html">Quick Start</a>
<a href="models.html">Models</a>
<span class="nav-drop-cat">SUPPORT /</span>
<a href="troubleshooting.html">Troubleshoot</a>
<a href="faq.html">FAQ</a>
<span class="nav-drop-cat">MORE /</span>
<a href="glossary.html">Glossary</a>
<a href="whats-new.html">What's New</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn">Admin ▾</button>
<div class="nav-drop-menu nav-drop-menu-wide">
<span class="nav-drop-cat">DOCS /</span>
<a href="security.html">Security & Privacy</a>
<a href="admin.html">Admin Guide</a>
<span class="nav-drop-cat">MONITOR /</span>
<a href="dashboard.html">Dashboard</a>
<a href="audit.html">Governance & Audit</a>
<a href="health.html">Health Center</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
<a href="teams.html">Teams</a>
<a href="models-admin.html">Model Catalog</a>
<span class="nav-drop-cat">TOOLS /</span>
<a href="apikeys.html">API Keys</a>
<a href="model-compare.html">Response Comparison</a>
<a href="api-playground.html">API Playground</a>
<a href="guardrails.html">Guardrails</a>
<span class="nav-drop-cat">SYSTEM /</span>
<a href="console.html">Console</a>
<a href="settings.html">Settings</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn active">AI Tools ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">MODEL BUILD /</span>
<a href="dataset-creation.html" class="active">Dataset Creation</a>
<a href="training.html">Model Training</a>
<span class="nav-drop-cat">INTELLIGENCE /</span>
<a href="documents.html">Document Intelligence</a>
<a href="chat-multi.html">AI Workspace</a>
<a href="prompt-studio.html">Prompt Studio</a>
<a href="meeting.html">Meeting Assistant</a>
<span class="nav-drop-cat">AUTOMATION /</span>
<a href="agents.html">Agent Studio</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Designer</a>
<span class="nav-drop-cat">QUALITY /</span>
<a href="evals.html">AI Eval Suite</a>
<a href="chatrooms.html">Chat Rooms</a>
</div>
</div>
</nav>
<a href="notifications.html" class="nav-icon-btn" style="position:relative" aria-label="Notifications"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M15 17h5l-1.4-1.4A2 2 0 0 1 18 14.2V11a6 6 0 1 0-12 0v3.2a2 2 0 0 1-.6 1.4L4 17h5"/><path d="M10 17a2 2 0 0 0 4 0"/></svg></span></a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
<div class="page-hero">
<div class="label">Admin · Dataset Preparation</div>
<h1>Dataset Creation</h1>
<p>Generate, review, and publish grounded datasets using local models.</p>
</div>
<div class="content">
<div class="tr-card">
<div class="tr-card-title">Create Dataset</div>
<div class="tr-card-sub">Generate grounded training samples from a source document with a local Ollama model. Review every row before publishing it to Dataset Manager.</div>
<div class="creator-grid">
<div>
<label class="lf-label">Dataset Name</label>
<input class="lf-input" id="dc-name" placeholder="e.g. hr-policy-qa-v1">
</div>
<div>
<label class="lf-label">Local Generation Model</label>
<select class="lf-select" id="dc-model"><option value="">Loading models…</option></select>
</div>
<div>
<label class="lf-label">Output Type</label>
<select class="lf-select" id="dc-format">
<option value="qa">Question &amp; Answer</option>
<option value="instruction">Instruction &amp; Response</option>
<option value="summary">Summary</option>
</select>
</div>
<div>
<label class="lf-label">Target Samples</label>
<input class="lf-input" id="dc-count" type="number" value="20" min="1" max="200">
</div>
<div class="full">
<label class="lf-label">Source Document</label>
<input class="lf-input" id="dc-file" type="file" accept=".pdf,.docx,.txt,.md,.csv">
<div class="lf-hint">PDF, DOCX, TXT, Markdown, or CSV — maximum 20 MB. Content stays on the appliance.</div>
</div>
<div class="full creator-actions">
<button class="tr-btn primary" id="dc-start" onclick="startDatasetCreation()">Generate Dataset</button>
<button class="tr-btn ghost" onclick="loadGenerationJobs()">Refresh</button>
<span class="lf-hint" id="dc-status"></span>
</div>
</div>
<div class="tr-table-wrap" style="margin-top:20px">
<table class="tr-table">
<thead><tr><th>Name</th><th>Source</th><th>Model</th><th>Progress</th><th>Status</th><th></th></tr></thead>
<tbody id="dc-jobs"><tr><td colspan="6" class="tr-empty">No generation jobs yet.</td></tr></tbody>
</table>
</div>
<div id="dc-review"></div>
</div>
</div><!-- /content -->
<footer>
<p data-brand="footer">Powered by Cezen</p>
</footer>
<div id="tr-toast"></div>
<script>
const _API = '/api';
const MOCK_DATASETS = [
{id:1, name:'hr_qa_pairs.jsonl', size_bytes:248000, row_count:412, created_at:'2026-05-20T10:00:00Z'},
{id:2, name:'finance_sops_qa.jsonl', size_bytes:189000, row_count:301, created_at:'2026-05-25T11:30:00Z'},
{id:3, name:'legal_clauses_qa.jsonl', size_bytes:542000, row_count:887, created_at:'2026-06-01T09:15:00Z'},
];
const MOCK_JOBS = [
{id:1, name:'HR Policy LoRA', base_model:'llama3:8b', dataset_name:'hr_qa_pairs.jsonl', status:'completed', epochs:3, loss_final:0.142, created_at:'2026-06-10T08:00:00Z', completed_at:'2026-06-10T14:22:00Z'},
{id:2, name:'Finance Expert', base_model:'mistral:7b', dataset_name:'finance_sops_qa.jsonl', status:'running', epochs:5, loss_final:null, created_at:'2026-06-28T06:00:00Z', completed_at:null},
{id:3, name:'Legal Classifier', base_model:'llama3:8b', dataset_name:'legal_clauses_qa.jsonl', status:'failed', epochs:2, loss_final:null, created_at:'2026-06-15T10:00:00Z', completed_at:null},
];
let datasetsCache = [];
let jobRefreshTimer = null;
let lossCharts = {};
let generationRefreshTimer = null;
// ── Helpers ──────────────────────────────────────────────────────────────────
function toast(msg, err=false) {
const t = document.getElementById('tr-toast');
t.textContent = msg;
t.className = err ? 'err' : '';
t.style.display = 'block';
setTimeout(() => t.style.display='none', 3500);
}
function fmt(iso) {
if (!iso) return '—';
const s = iso.replace(/\+00:00$/, 'Z').replace(/\.\d+Z$/, 'Z');
return new Date(s).toLocaleString([], {dateStyle:'short', timeStyle:'short'});
}
function fmtBytes(b) {
if (b < 1024) return b + ' B';
if (b < 1024*1024) return (b/1024).toFixed(1) + ' KB';
return (b/(1024*1024)).toFixed(1) + ' MB';
}
function statusBadge(s) {
return `<span class="badge-status ${s}">${s}</span>`;
}
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>'"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]));
}
async function startDatasetCreation() {
const name = document.getElementById('dc-name').value.trim();
const model = document.getElementById('dc-model').value;
const file = document.getElementById('dc-file').files[0];
if (!name || !model || !file) return toast('Dataset name, local model, and source document are required', true);
const button = document.getElementById('dc-start');
button.disabled = true;
const fd = new FormData();
fd.append('name', name); fd.append('model', model); fd.append('file', file);
fd.append('format', document.getElementById('dc-format').value);
fd.append('target_count', document.getElementById('dc-count').value);
try {
const res = await fetch(`${_API}/training/dataset-generation`, {method:'POST', credentials:'include', body:fd});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Could not start generation');
toast('Dataset generation started');
document.getElementById('dc-status').textContent = 'Generation is running locally. You can leave this page and return.';
loadGenerationJobs();
} catch (e) { toast(e.message, true); }
finally { button.disabled = false; }
}
async function loadGenerationJobs() {
const tbody = document.getElementById('dc-jobs');
try {
const res = await fetch(`${_API}/training/dataset-generation`, {credentials:'include'});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Could not load generation jobs');
const jobs = data.jobs || [];
tbody.innerHTML = jobs.length ? jobs.map(job => `<tr>
<td><strong>${escapeHtml(job.name)}</strong></td><td>${escapeHtml(job.source_name)}</td>
<td>${escapeHtml(job.model)}</td><td>${job.generated_count}/${job.target_count}</td>
<td>${statusBadge(job.status)}${job.error_msg ? `<div class="lf-hint">${escapeHtml(job.error_msg)}</div>` : ''}</td>
<td>${job.generated_count ? `<button class="tr-btn ghost" onclick="reviewGeneration(${job.id},'${escapeHtml(job.name)}')">Review</button>` : ''}</td>
</tr>`).join('') : '<tr><td colspan="6" class="tr-empty">No generation jobs yet.</td></tr>';
const running = jobs.some(job => ['pending','running'].includes(job.status));
if (running && !generationRefreshTimer) generationRefreshTimer = setInterval(loadGenerationJobs, 5000);
if (!running && generationRefreshTimer) { clearInterval(generationRefreshTimer); generationRefreshTimer = null; }
} catch (e) { tbody.innerHTML = `<tr><td colspan="6" class="tr-empty">${escapeHtml(e.message)}</td></tr>`; }
}
async function reviewGeneration(jobId, name) {
const review = document.getElementById('dc-review');
const res = await fetch(`${_API}/training/dataset-generation/${jobId}/samples`, {credentials:'include'});
const data = await res.json();
if (!res.ok) return toast(data.detail || 'Could not load samples', true);
const samples = data.samples || [];
review.innerHTML = `<div class="tr-toolbar" style="margin-top:22px"><strong>Review: ${escapeHtml(name)}</strong>
<button class="tr-btn ghost" onclick="setAllSamples(${jobId},'approved')">Approve all</button>
<button class="tr-btn primary" onclick="publishGeneration(${jobId})">Publish approved to Dataset Manager</button></div>` +
(samples.length ? samples.map(s => `<div class="sample-card" id="sample-${s.id}">
<div class="sample-head"><strong>Sample ${s.id}</strong>${statusBadge(s.status)}</div>
<div class="sample-fields"><div><label class="lf-label">Instruction</label><textarea class="lf-input" id="si-${s.id}">${escapeHtml(s.instruction)}</textarea></div>
<div><label class="lf-label">Output</label><textarea class="lf-input" id="so-${s.id}">${escapeHtml(s.output)}</textarea></div></div>
<details class="source-preview"><summary>Grounding source</summary>${escapeHtml(s.source_text)}</details>
<div class="creator-actions" style="margin-top:12px"><button class="tr-btn ghost" onclick="saveSample(${s.id},'approved')">Approve &amp; Save</button>
<button class="tr-btn danger" onclick="saveSample(${s.id},'rejected')">Reject</button></div></div>`).join('') : '<div class="tr-empty">No samples generated.</div>');
review.dataset.jobId = jobId; review.dataset.jobName = name;
}
async function saveSample(id, status) {
const body = {instruction:document.getElementById(`si-${id}`).value, output:document.getElementById(`so-${id}`).value, status};
const res = await fetch(`${_API}/training/dataset-generation/samples/${id}`, {method:'PATCH', credentials:'include', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)});
const data = await res.json().catch(()=>({}));
if (!res.ok) return toast(data.detail || 'Could not save sample', true);
toast(status === 'approved' ? 'Sample approved' : 'Sample rejected');
reviewGeneration(document.getElementById('dc-review').dataset.jobId, document.getElementById('dc-review').dataset.jobName);
}
async function setAllSamples(jobId, status) {
const cards = [...document.querySelectorAll('.sample-card')];
for (const card of cards) {
const id = Number(card.id.replace('sample-',''));
const body = {instruction:document.getElementById(`si-${id}`).value, output:document.getElementById(`so-${id}`).value, status};
const res = await fetch(`${_API}/training/dataset-generation/samples/${id}`, {method:'PATCH', credentials:'include', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)});
if (!res.ok) return toast('Could not update every sample', true);
}
toast('All samples approved');
reviewGeneration(jobId, document.getElementById('dc-review').dataset.jobName);
}
async function publishGeneration(jobId) {
const res = await fetch(`${_API}/training/dataset-generation/${jobId}/publish`, {method:'POST', credentials:'include'});
const data = await res.json().catch(()=>({}));
if (!res.ok) return toast(data.detail || 'Could not publish dataset', true);
toast(`Published ${data.filename} with ${data.row_count} rows`);
}
async function loadCreatorModels() {
const sel = document.getElementById("dc-model");
try {
const data = await fetch(`/models`, {credentials:"include"}).then(r => r.json());
const models = data.models || data || [];
sel.innerHTML = '<option value="">Select generation model…</option>';
(Array.isArray(models) ? models : []).forEach(m => {
const name = m.name || m;
const opt = document.createElement("option");
opt.value = name; opt.textContent = name; sel.appendChild(opt);
});
if (!models.length) sel.innerHTML = '<option value="">No local models available</option>';
} catch (e) {
sel.innerHTML = '<option value="">Could not load local models</option>';
}
}
// ── Init ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
loadCreatorModels();
loadGenerationJobs();
</script>
<script src="auth.js?v=20260721-consolidated"></script>
<script src="branding.js?v=20260721-consolidated"></script>
</body>
</html>