csc-engine / frontend /diary.html
josephrw's picture
Upload folder using huggingface_hub
ab27fab verified
Raw
History Blame Contribute Delete
15.8 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>CSC Engine — Lab Diary</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
font-family: 'SF Pro Display', system-ui, -apple-system, sans-serif;
--bg: #06080f;
--panel: #0b0e18;
--panel2: #0f1320;
--border: #1a1f2e;
--border2: #232a3d;
--accent: #00d4ff;
--accent2: #6c5ce7;
--green: #34d399;
--amber: #fbbf24;
--red: #f87171;
--gray: #6b7280;
--text: #e2e8f0;
--text2: #94a3b8;
--mono: 'SF Mono', 'JetBrains Mono', monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--bg);
color: var(--text);
}
body::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
background: radial-gradient(ellipse at 20% 0%, rgba(0, 212, 255, .04), transparent 50%),
radial-gradient(ellipse at 80% 100%, rgba(108, 92, 231, .04), transparent 50%);
}
main {
max-width: 1200px;
margin: 0 auto;
padding: 24px;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0 24px;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
}
.logo-mark {
width: 36px;
height: 36px;
border-radius: 8px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 800;
color: #000;
}
header h1 {
font-size: 24px;
font-weight: 700;
}
header h1 span {
color: var(--accent);
}
.sub {
color: var(--text2);
font-size: 13px;
margin-top: 2px;
}
.back-link {
padding: 8px 16px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
background: var(--panel2);
color: var(--text2);
border: 1px solid var(--border2);
text-decoration: none;
transition: .15s;
}
.back-link:hover {
background: #161b2d;
color: var(--text);
}
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 24px;
}
.stat {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px;
text-align: center;
}
.stat strong {
display: block;
font-size: 28px;
color: var(--accent);
font-family: var(--mono);
}
.stat small {
color: var(--text2);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
}
.entries {
display: flex;
flex-direction: column;
gap: 16px;
}
.entry {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
transition: .15s;
}
.entry:hover {
border-color: var(--border2);
}
.entry-header {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
background: linear-gradient(180deg, var(--panel2), transparent);
}
.entry-num {
width: 28px;
height: 28px;
border-radius: 6px;
background: rgba(0, 212, 255, .1);
color: var(--accent);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
font-family: var(--mono);
}
.entry-title {
font-size: 14px;
font-weight: 600;
flex: 1;
}
.entry-hash {
font-size: 11px;
color: var(--text2);
font-family: var(--mono);
}
.entry-time {
font-size: 11px;
color: var(--gray);
font-family: var(--mono);
}
.entry-body {
padding: 18px;
}
.entry-section {
margin-bottom: 14px;
}
.entry-section:last-child {
margin-bottom: 0;
}
.entry-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 6px;
}
.label-evidence {
color: var(--accent);
}
.label-reasoning {
color: var(--amber);
}
.label-code {
color: var(--green);
}
.label-output {
color: #a78bfa;
}
.label-audio {
color: #f472b6;
}
.entry-evidence {
font-family: var(--mono);
font-size: 12px;
color: var(--text2);
line-height: 1.6;
}
.entry-evidence li {
list-style: none;
padding-left: 16px;
position: relative;
margin: 2px 0;
}
.entry-evidence li::before {
content: '\25B8';
position: absolute;
left: 0;
color: var(--accent);
}
.entry-reasoning {
font-family: var(--mono);
font-size: 12px;
color: var(--text2);
line-height: 1.7;
white-space: pre-wrap;
}
.entry-code {
background: #060810;
border: 1px solid var(--border);
border-radius: 8px;
padding: 14px;
font-family: var(--mono);
font-size: 12px;
line-height: 1.6;
color: #c8d3f5;
overflow-x: auto;
}
.entry-output {
background: #04060c;
border: 1px solid var(--border);
border-radius: 8px;
padding: 14px;
font-family: var(--mono);
font-size: 12px;
line-height: 1.6;
color: var(--green);
overflow-x: auto;
}
.entry-output.err {
color: var(--red);
}
.entry-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.entry-btn {
padding: 6px 14px;
border-radius: 6px;
border: none;
cursor: pointer;
font-size: 11px;
font-weight: 600;
transition: .15s;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 4px;
}
.btn-run {
background: rgba(52, 211, 153, .15);
color: var(--green);
border: 1px solid rgba(52, 211, 153, .3);
}
.btn-run:hover {
background: rgba(52, 211, 153, .25);
}
.btn-view {
background: rgba(0, 212, 255, .1);
color: var(--accent);
border: 1px solid rgba(0, 212, 255, .2);
}
.btn-view:hover {
background: rgba(0, 212, 255, .2);
}
.btn-debug {
background: rgba(251, 191, 36, .15);
color: var(--amber);
border: 1px solid rgba(251, 191, 36, .3);
}
.btn-debug:hover {
background: rgba(251, 191, 36, .25);
}
.entry-audio {
margin-top: 10px;
}
.entry-audio audio {
width: 100%;
height: 36px;
}
.empty {
text-align: center;
padding: 60px;
color: var(--text2);
font-family: var(--mono);
}
.empty h2 {
font-size: 18px;
margin-bottom: 8px;
color: var(--text);
}
</style>
</head>
<body>
<main>
<header>
<div class="logo">
<div class="logo-mark">C</div>
<div>
<h1>Lab <span>Diary</span></h1>
<div class="sub">AI Notebook &mdash; computed artifacts with evidence, reasoning &amp; audio</div>
</div>
</div>
<a href="/" class="back-link">&larr; Back to Engine</a>
</header>
<div class="stats">
<div class="stat"><strong id="totalCount">0</strong><small>Artifacts</small></div>
<div class="stat"><strong id="runCount">0</strong><small>Executed</small></div>
<div class="stat"><strong id="audioCount">0</strong><small>With Audio</small></div>
<div class="stat"><strong id="successCount">0</strong><small>Exit 0</small></div>
</div>
<div class="entries" id="entries">
<div class="empty">
<h2>Loading artifacts...</h2>
<p>Fetching from /api/artifacts</p>
</div>
</div>
</main>
<script>
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text || "";
return div.innerHTML;
}
function formatTime(ts) {
if (!ts) return "unknown";
const d = new Date(ts * 1000);
return d.toLocaleString();
}
function parseSections(text) {
const sections = {};
const patterns = ['REASONING', 'SCENE', 'INFERRED_INTENT', 'INTENT', 'SIGNALS', 'CANDIDATE_TASK', 'UNCERTAINTY', 'EVIDENCE', 'CODE', 'RUN', 'TEST', 'ATTRIBUTION'];
const patStr = patterns.join('|');
for (const pat of patterns) {
const regex = new RegExp('\\*{0,2}' + pat + '\\*{0,2}:?\\s*([\\s\\S]*?)(?=\\n\\s*\\*{0,2}(?:' + patStr + ')\\*{0,2}:|\\n\\s*(?:INSUFFICIENT)|$)', 'i');
const m = text.match(regex);
if (m && m[1].trim()) sections[pat] = m[1].trim();
}
if (sections.CODE) {
const fenceMatch = sections.CODE.match(/```(?:python)?\s*([\s\S]*?)```/);
if (fenceMatch) sections.CODE = fenceMatch[1].trim();
}
return sections;
}
async function loadDiary() {
try {
const resp = await fetch('/api/artifacts?limit=50');
const data = await resp.json();
const artifacts = data.artifacts || [];
renderEntries(artifacts);
} catch (e) {
document.getElementById('entries').innerHTML = '<div class="empty"><h2>Error</h2><p>' + escapeHtml(e.message) + '</p></div>';
}
}
function renderEntries(artifacts) {
const container = document.getElementById('entries');
let runCount = 0, audioCount = 0, successCount = 0;
if (artifacts.length === 0) {
container.innerHTML = '<div class="empty"><h2>No artifacts yet</h2><p>Start the engine and generate code to populate the diary.</p></div>';
document.getElementById('totalCount').textContent = '0';
document.getElementById('runCount').textContent = '0';
document.getElementById('audioCount').textContent = '0';
document.getElementById('successCount').textContent = '0';
return;
}
let html = '';
artifacts.forEach((art, i) => {
const num = artifacts.length - i;
const hash = art.patch_hash || 'unknown';
const hasRun = art.exit_code !== undefined;
const hasAudio = !!art.audio_path;
const success = art.exit_code === 0;
if (hasRun) runCount++;
if (hasAudio) audioCount++;
if (success) successCount++;
// Parse sections from observer_output if available
const sections = parseSections(art.observer_output || '');
const evidence = art.evidence || sections.EVIDENCE || '';
const reasoning = art.reasoning || sections.REASONING || '';
const code = art.code || sections.CODE || '';
const stdout = art.stdout || '';
const stderr = art.stderr || '';
html += '<div class="entry">';
html += '<div class="entry-header">';
html += '<div class="entry-num">' + num + '</div>';
html += '<div class="entry-title">Artifact ' + hash.slice(0, 12) + '</div>';
html += '<div class="entry-hash">' + hash.slice(0, 16) + '</div>';
html += '<div class="entry-time">' + formatTime(art.timestamp) + '</div>';
html += '</div>';
html += '<div class="entry-body">';
if (evidence) {
html += '<div class="entry-section">';
html += '<div class="entry-label label-evidence">\u258C Evidence</div>';
html += '<ul class="entry-evidence">';
for (const line of evidence.split('\n').filter(l => l.trim())) {
html += '<li>' + escapeHtml(line.replace(/^[-\u2022*]\s*/, '')) + '</li>';
}
html += '</ul></div>';
}
if (reasoning) {
html += '<div class="entry-section">';
html += '<div class="entry-label label-reasoning">\u258C Reasoning</div>';
html += '<div class="entry-reasoning">' + escapeHtml(reasoning) + '</div>';
html += '</div>';
}
if (code) {
html += '<div class="entry-section">';
html += '<div class="entry-label label-code">\u258C Code</div>';
html += '<pre class="entry-code">' + escapeHtml(code) + '</pre>';
html += '</div>';
}
if (stdout) {
html += '<div class="entry-section">';
html += '<div class="entry-label label-output">\u258C Output (exit ' + art.exit_code + ')</div>';
html += '<pre class="entry-output">' + escapeHtml(stdout) + '</pre>';
html += '</div>';
}
if (stderr) {
html += '<div class="entry-section">';
html += '<div class="entry-label label-output" style="color:var(--red)">\u258C Errors</div>';
html += '<pre class="entry-output err">' + escapeHtml(stderr) + '</pre>';
html += '</div>';
}
if (hasAudio) {
html += '<div class="entry-section">';
html += '<div class="entry-label label-audio">\u258C Audio Recording</div>';
html += '<div class="entry-audio"><audio controls src="/audio/' + art.audio_path + '"></audio></div>';
html += '</div>';
}
html += '<div class="entry-actions">';
html += '<button class="entry-btn btn-run" onclick="runFromDiary(\'' + hash + '\', this)">\u25B6 Run</button>';
html += '<button class="entry-btn btn-debug" onclick="debugFromDiary(\'' + hash + '\', this)">\u2691 Debug &amp; Fix</button>';
html += '<a class="entry-btn btn-view" href="/artifact/' + hash + '" target="_blank">View Artifact</a>';
html += '</div>';
html += '</div></div>';
});
container.innerHTML = html;
document.getElementById('totalCount').textContent = artifacts.length;
document.getElementById('runCount').textContent = runCount;
document.getElementById('audioCount').textContent = audioCount;
document.getElementById('successCount').textContent = successCount;
}
async function runFromDiary(patchHash, btn) {
if (btn) { btn.disabled = true; btn.textContent = 'Running...'; }
try {
const resp = await fetch('/run?patch_hash=' + patchHash, { method: 'POST' });
const data = await resp.json();
if (data.exit_code === 0) {
btn.style.background = 'rgba(52,211,153,.3)';
btn.textContent = '\u2713 Success';
} else {
btn.style.background = 'rgba(248,113,113,.2)';
btn.style.color = 'var(--red)';
btn.textContent = '\u2717 Exit ' + data.exit_code;
}
// Reload diary after a moment
setTimeout(() => loadDiary(), 500);
} catch (e) {
btn.textContent = 'Error';
}
setTimeout(() => {
if (btn) { btn.disabled = false; btn.textContent = '\u25B6 Run'; btn.style.background = ''; btn.style.color = ''; }
}, 3000);
}
async function debugFromDiary(patchHash, btn) {
if (btn) { btn.disabled = true; btn.textContent = 'Debugging...'; }
try {
const resp = await fetch('/debug?patch_hash=' + patchHash, { method: 'POST' });
const data = await resp.json();
if (data.error) {
btn.textContent = 'Error';
} else {
btn.style.background = 'rgba(251,191,36,.3)';
btn.textContent = '\u2713 Fixed';
setTimeout(() => loadDiary(), 1000);
}
} catch (e) {
btn.textContent = 'Error';
}
setTimeout(() => {
if (btn) { btn.disabled = false; btn.textContent = '\u2691 Debug & Fix'; btn.style.background = ''; }
}, 3000);
}
loadDiary();
</script>
</body>
</html>