csc-engine / frontend /index.html
josephrw's picture
Upload folder using huggingface_hub
e8a76c8 verified
Raw
History Blame Contribute Delete
102 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>CSC Engine β€” Sensory Proprietary Compiler</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
font-family: 'SF Pro Display', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--bg: #050710;
--bg2: #080b16;
--panel: #0b0e1a;
--panel2: #0f1322;
--border: #1a1f2e;
--border2: #232a3d;
--accent: #00d4ff;
--accent2: #6c5ce7;
--accent3: #ff6b9d;
--green: #34d399;
--amber: #fbbf24;
--red: #f87171;
--gray: #6b7280;
--text: #e2e8f0;
--text2: #94a3b8;
--text3: #64748b;
--mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--bg);
color: var(--text);
overflow-x: hidden;
min-height: 100vh;
}
body::before {
content: '';
position: fixed;
inset: 0;
z-index: -2;
background:
radial-gradient(ellipse 80% 50% at 20% 0%, rgba(0, 212, 255, .06), transparent 60%),
radial-gradient(ellipse 60% 40% at 80% 100%, rgba(108, 92, 231, .06), transparent 60%),
radial-gradient(ellipse 40% 30% at 50% 50%, rgba(255, 107, 157, .03), transparent 60%);
}
body::after {
content: '';
position: fixed;
inset: 0;
z-index: -1;
background-image:
linear-gradient(rgba(26, 31, 46, .3) 1px, transparent 1px),
linear-gradient(90deg, rgba(26, 31, 46, .3) 1px, transparent 1px);
background-size: 60px 60px;
mask-image: radial-gradient(ellipse 100% 80% at 50% 0%, black 30%, transparent 70%);
-webkit-mask-image: radial-gradient(ellipse 100% 80% at 50% 0%, black 30%, transparent 70%);
}
/* ── NAV ── */
nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
background: rgba(5, 7, 16, .8);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
}
.nav-logo {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
}
.nav-logo-mark {
width: 28px;
height: 28px;
border-radius: 7px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 800;
color: #000;
}
.nav-logo h1 {
font-size: 16px;
font-weight: 700;
letter-spacing: -.3px;
}
.nav-logo h1 span {
color: var(--accent);
}
.nav-links {
display: flex;
gap: 4px;
align-items: center;
}
.nav-link {
padding: 6px 14px;
border-radius: 7px;
font-size: 12px;
font-weight: 600;
color: var(--text2);
cursor: pointer;
border: none;
background: none;
transition: .15s;
}
.nav-link:hover {
color: var(--text);
background: var(--panel2);
}
.nav-link.active {
color: var(--accent);
background: rgba(0, 212, 255, .08);
}
.nav-status {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
background: var(--panel2);
border: 1px solid var(--border2);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
}
.dot-idle {
background: var(--gray);
}
.dot-active {
background: var(--green);
box-shadow: 0 0 6px var(--green);
}
.dot-error {
background: var(--red);
box-shadow: 0 0 6px var(--red);
}
.dot-thinking {
background: var(--amber);
box-shadow: 0 0 6px var(--amber);
animation: pulse 1s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: .4;
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideLeft {
from {
opacity: 0;
transform: translateX(-8px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes glow {
0%,
100% {
box-shadow: 0 0 20px rgba(0, 212, 255, .1);
}
50% {
box-shadow: 0 0 40px rgba(0, 212, 255, .2);
}
}
@keyframes scan {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(100%);
}
}
/* ── VIEWS ── */
.view {
display: none;
padding-top: 56px;
min-height: 100vh;
}
.view.active {
display: block;
}
/* ── LANDING ── */
.landing {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
.hero {
text-align: center;
padding: 80px 0 60px;
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 14px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
background: rgba(0, 212, 255, .08);
border: 1px solid rgba(0, 212, 255, .2);
color: var(--accent);
margin-bottom: 24px;
}
.hero h2 {
font-size: 52px;
font-weight: 800;
letter-spacing: -1.5px;
line-height: 1.1;
margin-bottom: 20px;
}
.hero h2 .grad {
background: linear-gradient(135deg, var(--accent), var(--accent2), var(--accent3));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero p {
font-size: 18px;
color: var(--text2);
max-width: 640px;
margin: 0 auto 32px;
line-height: 1.6;
}
.hero-cta {
display: flex;
gap: 12px;
justify-content: center;
}
.btn {
padding: 10px 22px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 13px;
font-weight: 600;
transition: .15s;
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, var(--accent), var(--accent2));
color: #000;
}
.btn-primary:hover {
filter: brightness(1.15);
transform: translateY(-1px);
}
.btn-ghost {
background: var(--panel2);
color: var(--text);
border: 1px solid var(--border2);
}
.btn-ghost:hover {
background: #161b2d;
border-color: var(--accent);
}
.btn-danger {
background: rgba(248, 113, 113, .12);
color: var(--red);
border: 1px solid rgba(248, 113, 113, .25);
}
.btn-danger:hover {
background: rgba(248, 113, 113, .2);
}
.btn-sm {
padding: 6px 14px;
font-size: 11px;
border-radius: 7px;
}
/* ── ARCHITECTURE FLOW ── */
.arch-section {
padding: 40px 0;
}
.section-title {
font-size: 24px;
font-weight: 700;
text-align: center;
margin-bottom: 8px;
}
.section-sub {
text-align: center;
color: var(--text2);
font-size: 14px;
margin-bottom: 32px;
}
.arch-flow {
display: flex;
align-items: stretch;
gap: 0;
flex-wrap: wrap;
justify-content: center;
}
.arch-node {
flex: 1;
min-width: 140px;
max-width: 200px;
padding: 20px 16px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
text-align: center;
position: relative;
transition: .2s;
}
.arch-node:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.arch-node-icon {
font-size: 28px;
margin-bottom: 8px;
}
.arch-node-title {
font-size: 12px;
font-weight: 700;
color: var(--accent);
text-transform: uppercase;
letter-spacing: .8px;
margin-bottom: 6px;
}
.arch-node-desc {
font-size: 11px;
color: var(--text2);
line-height: 1.5;
}
.arch-arrow {
display: flex;
align-items: center;
color: var(--border2);
font-size: 20px;
padding: 0 4px;
}
/* ── STATS ── */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
padding: 40px 0;
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.hero h2 {
font-size: 36px;
}
.arch-flow {
flex-direction: column;
}
.arch-arrow {
transform: rotate(90deg);
padding: 8px 0;
}
}
.stat-card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px 20px;
text-align: center;
transition: .2s;
}
.stat-card:hover {
border-color: var(--border2);
}
.stat-value {
font-size: 36px;
font-weight: 800;
font-family: var(--mono);
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
font-size: 11px;
color: var(--text3);
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 4px;
}
/* ── FEATURES ── */
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding: 20px 0 60px;
}
@media (max-width: 900px) {
.features-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 600px) {
.features-grid {
grid-template-columns: 1fr;
}
}
.feature-card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
transition: .2s;
}
.feature-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.feature-icon {
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
margin-bottom: 12px;
}
.feature-title {
font-size: 14px;
font-weight: 700;
margin-bottom: 6px;
}
.feature-desc {
font-size: 12px;
color: var(--text2);
line-height: 1.6;
}
/* ── ENGINE DASHBOARD ── */
.dashboard {
max-width: 1800px;
margin: 0 auto;
padding: 16px 24px;
}
.controls {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
margin-bottom: 14px;
}
.ctrl-group {
display: flex;
align-items: center;
gap: 6px;
}
.ctrl-group label {
font-size: 10px;
color: var(--text3);
text-transform: uppercase;
letter-spacing: .5px;
}
.ctrl-group select,
.ctrl-group input {
background: var(--panel2);
color: var(--text);
border: 1px solid var(--border2);
border-radius: 6px;
padding: 5px 10px;
font-size: 12px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
@media (max-width: 1100px) {
.grid {
grid-template-columns: 1fr;
}
}
.pane {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.pane-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-bottom: 1px solid var(--border);
background: linear-gradient(180deg, var(--panel2), transparent);
}
.pane-title {
font-size: 11px;
font-weight: 700;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 1.2px;
display: flex;
align-items: center;
gap: 6px;
}
.pane-title .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
}
.pane-body {
padding: 14px;
overflow-y: auto;
max-height: 520px;
flex: 1;
}
.pane-body::-webkit-scrollbar {
width: 6px;
}
.pane-body::-webkit-scrollbar-track {
background: transparent;
}
.pane-body::-webkit-scrollbar-thumb {
background: var(--border2);
border-radius: 3px;
}
video,
canvas {
width: 100%;
border-radius: 8px;
border: 1px solid var(--border);
background: #000;
}
canvas {
display: none;
}
.metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
margin-top: 10px;
}
.metric {
background: var(--panel2);
border: 1px solid var(--border);
border-radius: 8px;
padding: 6px 8px;
text-align: center;
}
.metric strong {
display: block;
font-size: 16px;
color: #fff;
font-family: var(--mono);
}
.metric small {
color: var(--text3);
font-size: 9px;
text-transform: uppercase;
letter-spacing: .5px;
}
.qvd-bar {
height: 4px;
background: var(--panel2);
border-radius: 2px;
margin-top: 10px;
overflow: hidden;
}
.qvd-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent2), var(--accent));
transition: width .4s;
border-radius: 2px;
}
.qvd-label {
font-size: 10px;
color: var(--text3);
margin-top: 4px;
font-family: var(--mono);
}
/* ── AUDIO ETL PIPELINE ── */
.etl-pipeline {
display: flex;
align-items: center;
gap: 0;
padding: 16px 0;
flex-wrap: wrap;
justify-content: center;
}
.etl-stage {
flex: 1;
min-width: 120px;
max-width: 180px;
padding: 14px 12px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 10px;
text-align: center;
position: relative;
transition: .2s;
}
.etl-stage.active {
border-color: var(--accent);
box-shadow: 0 0 20px rgba(0, 212, 255, .1);
}
.etl-stage.done {
border-color: var(--green);
}
.etl-stage-icon {
font-size: 22px;
margin-bottom: 4px;
}
.etl-stage-title {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .8px;
color: var(--accent);
}
.etl-stage-status {
font-size: 9px;
color: var(--text3);
margin-top: 4px;
font-family: var(--mono);
}
.etl-arrow {
display: flex;
align-items: center;
color: var(--border2);
font-size: 16px;
padding: 0 3px;
}
/* ── SPEAKER ENTRIES ── */
.speaker-entry {
padding: 8px 12px;
margin: 4px 0;
border-radius: 8px;
font-size: 12px;
border-left: 3px solid;
animation: slideLeft .3s ease;
}
.speaker-user {
background: rgba(31, 111, 235, .08);
border-color: #1f6feb;
}
.speaker-background {
background: rgba(248, 113, 113, .08);
border-color: var(--red);
}
.speaker-unknown {
background: rgba(107, 114, 128, .08);
border-color: var(--gray);
}
.speaker-entry .label {
font-size: 9px;
color: var(--text3);
text-transform: uppercase;
letter-spacing: .5px;
margin-bottom: 2px;
}
.speaker-entry .text {
color: var(--text);
font-family: var(--mono);
font-size: 11px;
}
/* ── OBSERVER ── */
.observer-trace {
font-family: var(--mono);
font-size: 11px;
line-height: 1.7;
color: var(--text2);
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.observer-trace .section-header {
color: var(--accent);
font-weight: 700;
margin-top: 8px;
}
.observer-trace .reasoning {
color: var(--amber);
font-style: italic;
}
.observer-trace .intent {
color: var(--green);
}
/* ── CODE PATCHES ── */
.ide-patch {
background: #080a12;
border: 1px solid var(--border2);
border-radius: 10px;
margin: 8px 0;
overflow: hidden;
animation: slideIn .4s ease;
}
.ide-titlebar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #0c0f1a;
border-bottom: 1px solid var(--border);
}
.ide-dots {
display: flex;
gap: 5px;
}
.ide-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.ide-dot.r {
background: #ff5f57;
}
.ide-dot.y {
background: #febc2e;
}
.ide-dot.g {
background: #28c840;
}
.ide-filename {
font-size: 11px;
color: var(--text2);
font-family: var(--mono);
margin-left: 4px;
}
.ide-meta {
margin-left: auto;
display: flex;
gap: 10px;
font-size: 10px;
color: var(--text3);
font-family: var(--mono);
}
.ide-meta span {
color: var(--accent);
}
.ide-section {
padding: 10px 14px;
border-bottom: 1px solid var(--border);
}
.ide-section:last-child {
border-bottom: none;
}
.ide-section-label {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 4px;
}
.label-evidence {
color: var(--accent);
}
.label-reasoning {
color: var(--amber);
}
.label-code {
color: var(--green);
}
.label-run {
color: #a78bfa;
}
.label-test {
color: #f472b6;
}
.ide-evidence {
font-family: var(--mono);
font-size: 11px;
color: var(--text2);
line-height: 1.6;
}
.ide-evidence li {
margin: 2px 0;
list-style: none;
padding-left: 16px;
position: relative;
}
.ide-evidence li::before {
content: '\25B8';
position: absolute;
left: 0;
color: var(--accent);
}
.ide-reasoning {
font-family: var(--mono);
font-size: 11px;
color: var(--text2);
line-height: 1.7;
white-space: pre-wrap;
}
.ide-code {
font-family: var(--mono);
font-size: 12px;
line-height: 1.6;
color: #c8d3f5;
background: #060810;
padding: 12px 14px;
overflow-x: auto;
}
.ide-code .kw {
color: #c792ea;
}
.ide-code .str {
color: #c3e88d;
}
.ide-code .num {
color: #f78c6c;
}
.ide-code .com {
color: #546e7a;
font-style: italic;
}
.ide-code .fn {
color: #82aaff;
}
.ide-run,
.ide-test {
font-family: var(--mono);
font-size: 11px;
color: var(--text);
padding: 8px 14px;
background: #0a0d18;
display: flex;
align-items: center;
gap: 8px;
}
.ide-run .prompt {
color: var(--green);
}
.ide-test .prompt {
color: #f472b6;
}
.ide-terminal {
background: #04060c;
border-top: 1px solid var(--border);
padding: 10px 14px;
font-family: var(--mono);
font-size: 11px;
line-height: 1.6;
max-height: 200px;
overflow-y: auto;
}
.ide-terminal .term-line {
color: var(--text2);
}
.ide-terminal .term-stdout {
color: var(--green);
}
.ide-terminal .term-stderr {
color: var(--red);
}
.ide-terminal .term-meta {
color: var(--accent);
font-size: 10px;
margin-bottom: 4px;
}
.ide-run-btn {
padding: 3px 10px;
border-radius: 5px;
border: none;
cursor: pointer;
font-size: 10px;
font-weight: 700;
background: rgba(52, 211, 153, .15);
color: var(--green);
border: 1px solid rgba(52, 211, 153, .3);
transition: .15s;
margin-left: auto;
}
.ide-run-btn:hover {
background: rgba(52, 211, 153, .25);
}
.ide-run-btn:disabled {
opacity: .4;
cursor: wait;
}
.ide-artifact-link {
padding: 3px 10px;
border-radius: 5px;
border: none;
cursor: pointer;
font-size: 10px;
font-weight: 600;
background: rgba(0, 212, 255, .1);
color: var(--accent);
border: 1px solid rgba(0, 212, 255, .2);
text-decoration: none;
margin-left: 6px;
}
.ide-artifact-link:hover {
background: rgba(0, 212, 255, .2);
}
.empty-state {
color: var(--text3);
font-size: 12px;
padding: 24px;
text-align: center;
font-family: var(--mono);
}
/* ── TERMINAL VIEW ── */
.terminal-view {
max-width: 1400px;
margin: 0 auto;
padding: 16px 24px;
}
.terminal-container {
background: #03050a;
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
}
.terminal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 14px;
background: var(--panel2);
border-bottom: 1px solid var(--border);
}
.terminal-tabs {
display: flex;
gap: 2px;
}
.term-tab {
padding: 4px 12px;
border-radius: 5px 5px 0 0;
font-size: 11px;
font-family: var(--mono);
cursor: pointer;
border: none;
background: none;
color: var(--text3);
transition: .15s;
}
.term-tab.active {
background: var(--panel);
color: var(--accent);
}
.term-tab:hover {
color: var(--text);
}
.terminal-body {
flex: 1;
display: flex;
overflow: hidden;
}
.terminal-output {
flex: 1;
overflow-y: auto;
padding: 12px 16px;
font-family: var(--mono);
font-size: 13px;
line-height: 1.6;
color: #c8d3f5;
white-space: pre-wrap;
word-break: break-all;
}
.terminal-output::-webkit-scrollbar {
width: 8px;
}
.terminal-output::-webkit-scrollbar-track {
background: transparent;
}
.terminal-output::-webkit-scrollbar-thumb {
background: var(--border2);
border-radius: 4px;
}
.terminal-output .term-line {
min-height: 20px;
}
.terminal-output .term-prompt {
color: var(--accent);
}
.terminal-output .term-err {
color: var(--red);
}
.terminal-output .term-out {
color: var(--green);
}
.terminal-output .term-warn {
color: var(--amber);
}
.terminal-output .term-info {
color: var(--text2);
}
.terminal-sidebar {
width: 220px;
border-left: 1px solid var(--border);
padding: 12px;
overflow-y: auto;
flex-shrink: 0;
background: var(--bg2);
}
.terminal-sidebar h4 {
font-size: 10px;
color: var(--text3);
text-transform: uppercase;
margin-bottom: 8px;
}
.ts-row {
padding: 4px 0;
color: var(--text2);
display: flex;
justify-content: space-between;
font-size: 11px;
}
.ts-row .ts-val {
color: var(--text);
font-family: var(--mono);
}
.ts-btn {
width: 100%;
padding: 6px;
margin-top: 6px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--panel);
color: var(--text2);
font-size: 11px;
cursor: pointer;
text-align: center;
transition: .15s;
}
.ts-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
.terminal-input-row {
display: flex;
align-items: center;
padding: 8px 16px;
gap: 10px;
border-top: 1px solid var(--border);
background: var(--panel2);
}
.terminal-input-row .prompt {
color: var(--accent);
font-family: var(--mono);
font-size: 13px;
font-weight: 600;
}
.terminal-input {
flex: 1;
background: none;
border: none;
outline: none;
color: var(--text);
font-family: var(--mono);
font-size: 13px;
}
.terminal-history {
max-height: 120px;
overflow-y: auto;
padding: 8px 16px;
border-top: 1px solid var(--border);
background: var(--bg2);
}
.terminal-history h4 {
font-size: 9px;
color: var(--text3);
text-transform: uppercase;
margin-bottom: 4px;
}
.terminal-history-item {
font-family: var(--mono);
font-size: 11px;
color: var(--text2);
padding: 2px 0;
cursor: pointer;
}
.terminal-history-item:hover {
color: var(--accent);
}
/* ── COMMAND PALETTE ── */
.cmd-palette {
position: fixed;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 520px;
max-width: 90vw;
z-index: 200;
background: var(--panel);
border: 1px solid var(--border2);
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
display: none;
}
.cmd-palette.open {
display: block;
}
.cmd-palette input {
width: 100%;
padding: 14px 16px;
background: none;
border: none;
outline: none;
color: var(--text);
font-size: 15px;
border-bottom: 1px solid var(--border);
}
.cmd-palette-results {
max-height: 320px;
overflow-y: auto;
}
.cmd-item {
padding: 10px 16px;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--border);
transition: .1s;
}
.cmd-item:hover,
.cmd-item.selected {
background: var(--panel2);
}
.cmd-item .cmd-icon {
width: 20px;
color: var(--accent);
font-size: 14px;
}
.cmd-item .cmd-label {
font-size: 13px;
color: var(--text);
}
.cmd-item .cmd-desc {
font-size: 11px;
color: var(--text3);
margin-left: auto;
}
.cmd-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, .4);
z-index: 199;
display: none;
}
.cmd-overlay.open {
display: block;
}
.kbd {
padding: 1px 5px;
border-radius: 3px;
background: var(--panel2);
border: 1px solid var(--border);
font-size: 10px;
font-family: var(--mono);
color: var(--text3);
}
.toolbar-btn {
padding: 4px 10px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--panel);
color: var(--text2);
font-size: 11px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
transition: .15s;
}
.toolbar-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
/* ── AUDIO RECORDER ── */
.audio-recorder {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin-bottom: 14px;
}
.rec-button {
width: 60px;
height: 60px;
border-radius: 50%;
border: 3px solid var(--red);
background: rgba(248, 113, 113, .15);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: .2s;
position: relative;
}
.rec-button:hover {
background: rgba(248, 113, 113, .25);
}
.rec-button.recording {
background: var(--red);
animation: pulse 1s infinite;
}
.rec-button-inner {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--red);
transition: .2s;
}
.rec-button.recording .rec-button-inner {
border-radius: 4px;
width: 16px;
height: 16px;
background: #fff;
}
.rec-info {
display: flex;
align-items: center;
gap: 16px;
}
.rec-time {
font-family: var(--mono);
font-size: 20px;
font-weight: 700;
color: var(--text);
}
.rec-status-text {
font-size: 12px;
color: var(--text2);
}
.waveform {
height: 40px;
display: flex;
align-items: center;
gap: 2px;
flex: 1;
max-width: 300px;
}
.wave-bar {
flex: 1;
background: var(--accent);
border-radius: 2px;
min-height: 4px;
transition: height .1s;
}
</style>
</head>
<body>
<!-- NAV -->
<nav>
<div class="nav-logo" onclick="switchView('landing')">
<div class="nav-logo-mark">C</div>
<h1>CSC <span>Engine</span></h1>
</div>
<div class="nav-links">
<button class="nav-link" data-view="landing" onclick="switchView('landing')">Home</button>
<button class="nav-link" data-view="engine" onclick="switchView('engine')">Engine</button>
<button class="nav-link" data-view="terminal" onclick="switchView('terminal')">Terminal</button>
<button class="nav-link" data-view="ide" onclick="switchView('ide')">IDE</button>
<a href="/diary" class="nav-link" style="text-decoration:none">Diary</a>
<a href="/api/help" class="nav-link" style="text-decoration:none">API</a>
</div>
<div class="nav-status" id="navStatus">
<span class="status-dot dot-idle"></span>
<span id="navStatusText">Idle</span>
</div>
</nav>
<!-- ═══════════════ LANDING VIEW ═══════════════ -->
<div class="view active" id="view-landing">
<div class="landing">
<div class="hero">
<div class="hero-badge">
<span
style="width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent)"></span>
Sensory Proprietary Compiler V1
</div>
<h2>Turn <span class="grad">sensory input</span><br>into executable code</h2>
<p>Camera frames, audio recordings, and speech transcripts flow through a dual-LLM pipeline β€” observer reasons
about what it sees and hears, builder generates code patches with cryptographic receipts. Never returns zero
artifact.</p>
<div class="hero-cta">
<button class="btn btn-primary" onclick="switchView('engine')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polygon points="5 3 19 12 5 21 5 3" />
</svg>
Launch Engine
</button>
<button class="btn btn-ghost" onclick="switchView('terminal')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="4 17 10 11 4 5" />
<line x1="12" y1="19" x2="20" y2="19" />
</svg>
Open Terminal
</button>
<a href="/ide" class="btn btn-ghost" style="text-decoration:none">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M16 18v6M8 18v6M12 2v20M2 8h20" />
</svg>
Web IDE
</a>
</div>
</div>
<!-- ARCHITECTURE FLOW -->
<div class="arch-section">
<div class="section-title">Audio β†’ LLM β†’ Code Artifact ETL Pipeline</div>
<div class="section-sub">Every recording is transcribed, analyzed, and compiled into a receipt-bearing artifact
</div>
<div class="arch-flow">
<div class="arch-node">
<div class="arch-node-icon">🎀</div>
<div class="arch-node-title">Capture</div>
<div class="arch-node-desc">Microphone / MediaRecorder captures webm audio in browser</div>
</div>
<div class="arch-arrow">β†’</div>
<div class="arch-node">
<div class="arch-node-icon">πŸ“</div>
<div class="arch-node-title">Transcribe</div>
<div class="arch-node-desc">SpeechRecognition API + speaker diarization assigns labels</div>
</div>
<div class="arch-arrow">β†’</div>
<div class="arch-node">
<div class="arch-node-icon">🧠</div>
<div class="arch-node-title">Observe</div>
<div class="arch-node-desc">Observer LLM compresses sensory state, infers intent</div>
</div>
<div class="arch-arrow">β†’</div>
<div class="arch-node">
<div class="arch-node-icon">βš™οΈ</div>
<div class="arch-node-title">Build</div>
<div class="arch-node-desc">Builder LLM generates code patch with evidence & reasoning</div>
</div>
<div class="arch-arrow">β†’</div>
<div class="arch-node">
<div class="arch-node-icon">πŸ“œ</div>
<div class="arch-node-title">Receipt</div>
<div class="arch-node-desc">SHA-256 receipt with feature attribution & provenance</div>
</div>
</div>
</div>
<!-- STATS -->
<div class="stats-grid" id="statsGrid">
<div class="stat-card">
<div class="stat-value" id="statLangs">31</div>
<div class="stat-label">Languages</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statEndpoints">112</div>
<div class="stat-label">API Endpoints</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statArtifacts">0</div>
<div class="stat-label">Artifacts</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statReceipts">0</div>
<div class="stat-label">Receipts</div>
</div>
</div>
<!-- FEATURES -->
<div class="arch-section">
<div class="section-title">Capabilities</div>
<div class="section-sub">Full-cycle terminal, IDE, and sensory compiler in one deployment</div>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon" style="background:rgba(0,212,255,.1);color:var(--accent)">🎀</div>
<div class="feature-title">Audio ETL Pipeline</div>
<div class="feature-desc">Record audio β†’ transcribe β†’ LLM observe β†’ build code β†’ generate receipt.
Standalone or linked to artifacts.</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(108,92,231,.1);color:var(--accent2)">πŸ–₯</div>
<div class="feature-title">Serious Terminal</div>
<div class="feature-desc">Full-height terminal with command history, session management, long-running
process support (300s timeout).</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(52,211,153,.1);color:var(--green)">⚑</div>
<div class="feature-title">31 Language Runner</div>
<div class="feature-desc">Python, R, Go, Rust, C, C++, Java, Ruby, Haskell, Elixir, Julia, Dart, and more β€”
all with auto-detection.</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(251,191,36,.1);color:var(--amber)">🧠</div>
<div class="feature-title">Dual-LLM Architecture</div>
<div class="feature-desc">Observer LLM reasons about sensory input. Builder LLM generates code. Fallback
ladder ensures output at every level.</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(255,107,157,.1);color:var(--accent3)">πŸ“œ</div>
<div class="feature-title">Cryptographic Receipts</div>
<div class="feature-desc">Every artifact gets a SHA-256 receipt with frame hashes, audio chunk hashes,
speaker segments, and provenance.</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(167,139,250,.1);color:#a78bfa">πŸ”</div>
<div class="feature-title">Tamper-Evident Audit</div>
<div class="feature-desc">SHA-256 chained audit log. Every action recorded with chain verification.
Backup/restore workspaces.</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(0,212,255,.1);color:var(--accent)">πŸ“Š</div>
<div class="feature-title">Metrics & Monitoring</div>
<div class="feature-desc">Prometheus-style metrics, deep health checks, request logging middleware,
dependency status.</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(52,211,153,.1);color:var(--green)">🌐</div>
<div class="feature-title">Web IDE</div>
<div class="feature-desc">Browser-based code editor with terminal, language selector, and live execution. No
install required.</div>
</div>
<div class="feature-card">
<div class="feature-icon" style="background:rgba(248,113,113,.1);color:var(--red)">πŸ”Œ</div>
<div class="feature-title">GPT Custom Actions</div>
<div class="feature-desc">OpenAPI 3.1 schema at /gpt-actions.json. 112 endpoints accessible from ChatGPT
custom GPTs.</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ ENGINE VIEW ═══════════════ -->
<div class="view" id="view-engine">
<div class="dashboard">
<!-- AUDIO ETL PIPELINE BAR -->
<div class="audio-recorder">
<div style="display:flex;align-items:center;gap:20px;flex-wrap:wrap">
<div class="rec-button" id="recButton" onclick="toggleRecording()">
<div class="rec-button-inner"></div>
</div>
<div class="rec-info">
<div>
<div class="rec-time" id="recTime">00:00</div>
<div class="rec-status-text" id="recStatus">Click to record</div>
</div>
</div>
<div class="waveform" id="waveform"></div>
<div style="display:flex;gap:8px;align-items:center">
<button class="btn btn-sm btn-ghost" id="speechBtn" onclick="startSpeech()">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
<line x1="12" y1="19" x2="12" y2="23" />
</svg>
Start Speech
</button>
<button class="btn btn-sm btn-ghost" id="ttsBtn" onclick="toggleTTS()"
style="border-color:var(--accent);color:var(--accent)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
<path d="M19.07 4.93a10 10 0 0 1 0 14.14" />
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
</svg>
TTS: On
</button>
<button class="btn btn-sm btn-ghost" onclick="switchView('terminal')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="4 17 10 11 4 5" />
<line x1="12" y1="19" x2="20" y2="19" />
</svg>
Terminal
</button>
</div>
</div>
<!-- ETL STAGES -->
<div class="etl-pipeline" style="margin-top:16px">
<div class="etl-stage" id="etlCapture">
<div class="etl-stage-icon">🎀</div>
<div class="etl-stage-title">Capture</div>
<div class="etl-stage-status" id="etlCaptureStatus">idle</div>
</div>
<div class="etl-arrow">β†’</div>
<div class="etl-stage" id="etlTranscribe">
<div class="etl-stage-icon">πŸ“</div>
<div class="etl-stage-title">Transcribe</div>
<div class="etl-stage-status" id="etlTranscribeStatus">idle</div>
</div>
<div class="etl-arrow">β†’</div>
<div class="etl-stage" id="etlObserve">
<div class="etl-stage-icon">🧠</div>
<div class="etl-stage-title">Observe</div>
<div class="etl-stage-status" id="etlObserveStatus">idle</div>
</div>
<div class="etl-arrow">β†’</div>
<div class="etl-stage" id="etlBuild">
<div class="etl-stage-icon">βš™οΈ</div>
<div class="etl-stage-title">Build</div>
<div class="etl-stage-status" id="etlBuildStatus">idle</div>
</div>
<div class="etl-arrow">β†’</div>
<div class="etl-stage" id="etlReceipt">
<div class="etl-stage-icon">πŸ“œ</div>
<div class="etl-stage-title">Receipt</div>
<div class="etl-stage-status" id="etlReceiptStatus">idle</div>
</div>
</div>
<!-- LIVE TRANSCRIPT DURING RECORDING -->
<div id="liveTranscript"
style="margin-top:12px;padding:10px 14px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;font-family:var(--mono);font-size:12px;color:var(--text2);min-height:40px;display:none">
<div style="font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:1px;margin-bottom:4px">
Live Transcript</div>
<div id="liveTranscriptText" style="line-height:1.6">Waiting for speech...</div>
</div>
<!-- RECORDED AUDIO LIST -->
<div id="audioListPanel" style="margin-top:12px">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
<div style="font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:1px;font-weight:700">
Recorded Audio Files</div>
<button class="toolbar-btn" onclick="loadAudioList()"
style="font-size:10px;padding:2px 8px">Refresh</button>
</div>
<div id="audioList" style="display:flex;flex-direction:column;gap:4px"></div>
</div>
</div>
<!-- CONTROLS -->
<div class="controls">
<button id="startBtn" class="btn btn-primary btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polygon points="5 3 19 12 5 21 5 3" />
</svg>
Start Engine
</button>
<button id="stopBtn" class="btn btn-danger btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="6" y="6" width="12" height="12" />
</svg>
Stop
</button>
<button id="forceBtn" class="btn btn-ghost btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
</svg>
Force Generate
</button>
<div class="ctrl-group">
<label>Mode</label>
<select id="mode">
<option value="continuous_code">continuous_code</option>
<option value="debug_visible_error">debug_visible_error</option>
<option value="derive_pipeline">derive_pipeline</option>
<option value="research_prism">research_prism</option>
</select>
</div>
<div class="ctrl-group">
<label>Camera</label>
<select id="camSelect" style="width:140px">
<option value="">Loading...</option>
</select>
</div>
<div class="ctrl-group">
<label>Video</label>
<input type="file" id="videoFile" accept="video/*" style="width:120px;font-size:10px" />
</div>
<div class="ctrl-group">
<label>Frame ms</label>
<input id="frameMs" value="800" style="width:55px" />
</div>
<div class="ctrl-group">
<label>Auto-Run</label>
<input type="checkbox" id="autoRun" checked />
</div>
</div>
<!-- 4-PANE GRID -->
<div class="grid">
<div class="pane">
<div class="pane-header">
<div class="pane-title"><span class="dot"></span>Camera Stream</div>
<div style="font-size:10px;color:var(--text3);font-family:var(--mono)" id="camStatus">off</div>
</div>
<div class="pane-body">
<video id="video" autoplay playsinline muted></video>
<canvas id="canvas"></canvas>
<div class="metrics">
<div class="metric"><strong id="entropy">0.0</strong><small>entropy</small></div>
<div class="metric"><strong id="motion">0.0</strong><small>motion</small></div>
<div class="metric"><strong id="fps">1.0</strong><small>fps</small></div>
<div class="metric"><strong id="buffered">0</strong><small>frames</small></div>
</div>
<div class="qvd-bar">
<div id="qvdFill" class="qvd-fill" style="width:0%"></div>
</div>
<div class="qvd-label">QVD: <span id="qvdVal">0.000</span> / threshold <span id="qvdThreshold">0.30</span>
&middot; novelty <span id="noveltyVal">0.000</span></div>
</div>
</div>
<div class="pane">
<div class="pane-header">
<div class="pane-title"><span class="dot"></span>Audio / Speaker Ledger</div>
<div style="font-size:10px;color:var(--text3);font-family:var(--mono)" id="audioStatus">silent</div>
</div>
<div class="pane-body" id="speakerLedger">
<div class="empty-state">No audio yet. Click the record button or start speech to feed evidence.</div>
</div>
</div>
<div class="pane">
<div class="pane-header">
<div class="pane-title"><span class="dot"></span>Observer β€” Live Reasoning</div>
<div style="font-size:10px;color:var(--text3);font-family:var(--mono)" id="observerStatus">waiting</div>
</div>
<div class="pane-body" id="observerPane">
<div class="empty-state">Observer will reason about what the camera sees and audio hears.</div>
</div>
</div>
<div class="pane">
<div class="pane-header">
<div class="pane-title"><span class="dot"></span>Code Patches β€” Micro IDE</div>
<div style="font-size:10px;color:var(--text3);font-family:var(--mono)" id="patchCount">0 patches</div>
</div>
<div class="pane-body" id="patchStream">
<div class="empty-state">No patches yet. Camera movement or sound will trigger code generation.</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ TERMINAL VIEW ═══════════════ -->
<div class="view" id="view-terminal">
<div class="terminal-view">
<div class="terminal-container">
<div class="terminal-header">
<div class="terminal-tabs">
<div class="term-tab active" id="termTab1">Terminal 1</div>
</div>
<div style="display:flex;gap:8px;align-items:center">
<span style="font-size:10px;color:var(--text3)" id="termStatus">no session</span>
<button class="toolbar-btn" onclick="createTerminalSession()" style="font-size:10px;padding:3px 10px">+ New
Session</button>
<button class="toolbar-btn" onclick="killTerminalSession()"
style="font-size:10px;padding:3px 10px;color:var(--red);border-color:rgba(248,113,113,.2)">Kill</button>
</div>
</div>
<div class="terminal-body">
<div class="terminal-output" id="termOutput">
<div class="term-line term-info">CSC Engine Terminal β€” Kali Linux</div>
<div class="term-line term-info">Click "+ New Session" to create a terminal session.</div>
<div class="term-line term-info">Supports long-running processes (300s timeout), backtesting, and more.
</div>
<div class="term-line"></div>
</div>
<div class="terminal-sidebar">
<h4>Session Info</h4>
<div class="ts-row"><span>Status</span><span class="ts-val" id="tsStatus">β€”</span></div>
<div class="ts-row"><span>Uptime</span><span class="ts-val" id="tsUptime">β€”</span></div>
<div class="ts-row"><span>Lines</span><span class="ts-val" id="tsLines">β€”</span></div>
<div class="ts-row"><span>Last cmd</span><span class="ts-val" id="tsLastCmd">β€”</span></div>
<div style="margin-top:12px"></div>
<h4>Quick Actions</h4>
<button class="ts-btn" onclick="termExec('ls -la')">ls -la</button>
<button class="ts-btn" onclick="termExec('python3 --version')">python3 --version</button>
<button class="ts-btn" onclick="termExec('whoami && hostname')">whoami</button>
<button class="ts-btn" onclick="termExec('uname -a')">uname -a</button>
<button class="ts-btn" onclick="termExec('df -h /')">df -h</button>
<button class="ts-btn" onclick="termExec('free -m')">free -m</button>
<button class="ts-btn" onclick="pollTerminalOutput()">Refresh Output</button>
</div>
</div>
<div class="terminal-history" id="termHistoryPanel" style="display:none">
<h4>Command History (click to rerun)</h4>
<div id="termHistoryList"></div>
</div>
<div class="terminal-input-row">
<span class="prompt">csc@kali:~$</span>
<input class="terminal-input" id="termInput" placeholder="Type a command and press Enter..."
onkeydown="handleTermInput(event)" disabled />
</div>
</div>
</div>
</div>
<!-- ═══════════════ IDE VIEW ═══════════════ -->
<div class="view" id="view-ide">
<div style="max-width:1400px;margin:0 auto;padding:16px 24px">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">
<h2 style="font-size:20px;font-weight:700">Web IDE</h2>
<a href="/ide" class="btn btn-ghost btn-sm" style="text-decoration:none">Open Full IDE β†’</a>
</div>
<iframe src="/ide"
style="width:100%;height:calc(100vh - 140px);border:1px solid var(--border);border-radius:12px;background:#03050a"
frameborder="0"></iframe>
</div>
</div>
<!-- COMMAND PALETTE -->
<div class="cmd-overlay" id="cmdOverlay" onclick="closeCmdPalette()"></div>
<div class="cmd-palette" id="cmdPalette">
<input type="text" id="cmdInput" placeholder="Search commands..." oninput="filterCmds()"
onkeydown="handleCmdKey(event)" />
<div class="cmd-palette-results" id="cmdResults"></div>
</div>
<script>
// ── VIEW SWITCHING ──
function switchView(name) {
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
const view = document.getElementById('view-' + name);
if (view) view.classList.add('active');
const link = document.querySelector(`.nav-link[data-view="${name}"]`);
if (link) link.classList.add('active');
if (name === 'engine') loadAudioList();
}
// ── STATUS ──
function setStatus(msg, dotClass) {
const nav = document.getElementById('navStatus');
nav.innerHTML = '<span class="status-dot ' + (dotClass || 'dot-idle') + '"></span><span>' + msg + '</span>';
}
// ── LANDING STATS ──
async function loadStats() {
try {
const resp = await fetch('/api/artifacts?limit=1');
const data = await resp.json();
const count = data.total || data.artifacts?.length || 0;
document.getElementById('statArtifacts').textContent = count;
} catch (e) { }
try {
const resp = await fetch('/receipts?limit=1');
const data = await resp.json();
const count = data.total || data.receipts?.length || 0;
document.getElementById('statReceipts').textContent = count;
} catch (e) { }
try {
const resp = await fetch('/lang/list');
const data = await resp.json();
const avail = Object.values(data.languages || {}).filter(l => l.available).length;
document.getElementById('statLangs').textContent = avail;
} catch (e) { }
}
loadStats();
// ── ENGINE: CAMERA + WS ──
const video = document.getElementById("video");
const canvas = document.getElementById("canvas");
const speakerLedger = document.getElementById("speakerLedger");
const observerPane = document.getElementById("observerPane");
const patchStream = document.getElementById("patchStream");
const camSelect = document.getElementById("camSelect");
const videoFileEl = document.getElementById("videoFile");
let stream = null, ws = null, frameTimer = null, recognition = null, sessionId = null;
let patchCount = 0;
let mediaRecorder = null, audioChunks = [], currentAudioHash = null, audioStream = null;
let recTimer = null, recSeconds = 0, waveformTimer = null;
let lastPatchHash = null, lastPatchCode = null;
let termHistory = [];
async function populateCameras() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoInputs = devices.filter(d => d.kind === "videoinput");
camSelect.innerHTML = "";
videoInputs.forEach((d, i) => {
const label = d.label || ("Camera " + (i + 1));
const opt = document.createElement("option");
opt.value = d.deviceId;
opt.textContent = label.length > 30 ? label.slice(0, 30) + "..." : label;
camSelect.appendChild(opt);
});
if (videoInputs.length === 0) camSelect.innerHTML = '<option value="">No cameras found</option>';
} catch (e) { camSelect.innerHTML = '<option value="">Permission needed</option>'; }
}
populateCameras();
camSelect.onchange = () => { if (stream) startCamera(); };
videoFileEl.onchange = (e) => {
const file = e.target.files[0];
if (!file) return;
const url = URL.createObjectURL(file);
video.src = url; video.loop = true; video.muted = true; video.play();
document.getElementById("camStatus").textContent = "video file";
if (stream) { stream.getTracks().forEach(t => t.stop()); stream = null; }
setStatus("Playing video file", "dot-active");
};
function wsUrl() {
const scheme = location.protocol === "https:" ? "wss" : "ws";
return scheme + "://" + location.host + "/ws/stream";
}
async function startCamera() {
const selectedId = camSelect.value;
const constraints = { video: { width: { ideal: 1280 }, height: { ideal: 720 } }, audio: true };
if (selectedId) constraints.video.deviceId = { exact: selectedId };
else constraints.video.facingMode = "environment";
stream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = stream;
document.getElementById("camStatus").textContent = "live";
populateCameras();
}
function connectWs() {
ws = new WebSocket(wsUrl());
ws.onopen = () => setStatus("Connected", "dot-active");
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "session":
sessionId = msg.session_id;
setStatus("Session " + sessionId.slice(0, 8), "dot-active");
break;
case "frame_ack":
document.getElementById("entropy").textContent = msg.entropy.toFixed(2);
document.getElementById("motion").textContent = msg.motion.toFixed(2);
document.getElementById("fps").textContent = msg.fps_adaptive.toFixed(1);
document.getElementById("buffered").textContent = msg.buffered;
break;
case "qvd":
document.getElementById("qvdVal").textContent = msg.value.toFixed(3);
document.getElementById("qvdThreshold").textContent = msg.threshold.toFixed(2);
document.getElementById("noveltyVal").textContent = msg.novelty.toFixed(3);
document.getElementById("qvdFill").style.width = Math.min(100, (msg.value / msg.threshold) * 50) + "%";
break;
case "transcript_ack":
addSpeakerEntry(msg.speaker, msg.chars, msg.chunk_id);
document.getElementById("audioStatus").textContent = "active";
setEtlStage('transcribe', 'done');
setEtlStage('observe', 'active');
break;
case "observer":
renderObserverTrace(msg.output, msg.novelty);
setStatus("Observer reasoning complete", "dot-active");
document.getElementById("observerStatus").textContent = "updated";
setEtlStage('observe', 'done');
setEtlStage('build', 'active');
speakText(msg.output);
break;
case "patch":
addPatchEntry(msg.output, msg.patch_hash, msg.receipt, msg.qvd);
patchCount++;
document.getElementById("patchCount").textContent = patchCount + " patch" + (patchCount !== 1 ? "es" : "");
setStatus("Code patch generated", "dot-active");
setEtlStage('build', 'done');
setEtlStage('receipt', 'done');
const sections = parseSections(msg.output);
const codeText = (sections.CODE || "").replace(/^```python\s*/i, "").replace(/^```\s*/, "").replace(/```$/, "").trim();
if (codeText && msg.patch_hash) {
const reasoningText = sections.REASONING || "";
const evidenceText = sections.EVIDENCE || "";
fetch("/store-artifact?patch_hash=" + msg.patch_hash + "&code=" + encodeURIComponent(codeText) + "&reasoning=" + encodeURIComponent(reasoningText) + "&evidence=" + encodeURIComponent(evidenceText) + "&observer_output=" + encodeURIComponent(msg.output), { method: "POST" }).catch(() => { });
if (currentAudioHash === null && audioChunks.length > 0) {
currentAudioHash = msg.patch_hash;
finishAudioUpload(msg.patch_hash);
}
if (document.getElementById("autoRun") && document.getElementById("autoRun").checked) {
setTimeout(() => runCode(msg.patch_hash, null), 800);
}
}
break;
case "error":
setStatus("Error: " + msg.message, "dot-error");
break;
case "gate_block":
const gateDiv = document.createElement("div");
gateDiv.className = "ide-patch";
gateDiv.style.borderColor = "var(--amber)";
gateDiv.innerHTML = '<div class="ide-titlebar" style="color:var(--amber)">\u26d4 Evidence Gate β€” Level ' + (msg.fallback_level || '?') + '</div>' +
'<div class="ide-section"><div style="font-size:12px;color:var(--text2);line-height:1.6">' +
'<strong>Reason:</strong> ' + escapeHtml(msg.reason) + '<br>' +
'<strong>Artifact type:</strong> ' + escapeHtml(msg.artifact_type || msg.evidence_type || 'fallback') + '</div></div>';
patchStream.prepend(gateDiv);
break;
case "state":
updateSpeakerLedger(msg.state);
break;
}
};
ws.onclose = () => setStatus("Disconnected", "dot-idle");
ws.onerror = () => setStatus("WebSocket error", "dot-error");
}
function captureFrameDataUrl() {
const w = video.videoWidth || 1280;
const h = video.videoHeight || 720;
canvas.width = w; canvas.height = h;
const ctx = canvas.getContext("2d");
ctx.drawImage(video, 0, 0, w, h);
return canvas.toDataURL("image/jpeg", 0.74);
}
function sendFrame(force) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
ws.send(JSON.stringify({ type: "frame", frame: captureFrameDataUrl(), mode: document.getElementById("mode").value, auto: true, force: !!force }));
}
async function startLive() {
await startCamera();
connectWs();
const ms = Math.max(300, Number(document.getElementById("frameMs").value || 800));
frameTimer = setInterval(() => sendFrame(false), ms);
setStatus("Live stream active", "dot-active");
setEtlStage('capture', 'active');
}
function stopLive() {
if (frameTimer) clearInterval(frameTimer);
frameTimer = null;
if (recognition) { recognition.stop(); recognition = null; }
if (stream) { stream.getTracks().forEach(t => t.stop()); stream = null; }
if (ws) ws.close();
document.getElementById("camStatus").textContent = "off";
document.getElementById("audioStatus").textContent = "silent";
setStatus("Stopped", "dot-idle");
}
function startSpeech() {
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) { setStatus("SpeechRecognition not available", "dot-error"); return; }
recognition = new SR();
recognition.lang = "en-US";
recognition.continuous = true;
recognition.interimResults = true;
recognition.onresult = (event) => {
let text = "";
for (let i = 0; i < event.results.length; i++) text += event.results[i][0].transcript + " ";
text = text.trim();
if (text && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "transcript", text: text }));
setEtlStage('transcribe', 'active');
}
};
recognition.onerror = (e) => setStatus("Speech error: " + e.error, "dot-error");
recognition.start();
setStatus("Speech recognition active", "dot-active");
document.getElementById("audioStatus").textContent = "listening";
}
// ── ETL STAGE VISUALIZATION ──
function setEtlStage(name, state) {
const stage = document.getElementById('etl' + name.charAt(0).toUpperCase() + name.slice(1));
const status = document.getElementById('etl' + name.charAt(0).toUpperCase() + name.slice(1) + 'Status');
if (!stage) return;
stage.classList.remove('active', 'done');
if (state === 'active') { stage.classList.add('active'); status.textContent = 'processing...'; }
else if (state === 'done') { stage.classList.add('done'); status.textContent = 'complete'; }
else status.textContent = 'idle';
}
// ── AUDIO RECORDING ──
function toggleRecording() {
if (mediaRecorder && mediaRecorder.state === "recording") {
mediaRecorder.stop();
clearInterval(recTimer);
clearInterval(waveformTimer);
document.getElementById("recButton").classList.remove("recording");
document.getElementById("recStatus").textContent = "Processing...";
document.getElementById("audioStatus").textContent = "processing";
} else {
startAudioRecording();
}
}
let recRecognition = null;
let recTranscriptText = "";
let recAudioContext = null;
let recAnalyser = null;
let recFreqData = null;
let recFreqSamples = [];
async function startAudioRecording() {
try {
audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioChunks = [];
currentAudioHash = null;
recTranscriptText = "";
recFreqSamples = [];
// Real audio analysis via Web Audio API
recAudioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = recAudioContext.createMediaStreamSource(audioStream);
recAnalyser = recAudioContext.createAnalyser();
recAnalyser.fftSize = 2048;
source.connect(recAnalyser);
recFreqData = new Uint8Array(recAnalyser.frequencyBinCount);
mediaRecorder = new MediaRecorder(audioStream);
mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) audioChunks.push(e.data); };
mediaRecorder.onstop = () => {
if (audioStream) { audioStream.getTracks().forEach(t => t.stop()); audioStream = null; }
if (recRecognition) { try { recRecognition.stop(); } catch (e) { } recRecognition = null; }
if (recAudioContext) { try { recAudioContext.close(); } catch (e) { } recAudioContext = null; }
// Compute audio features from collected frequency samples
const audioFeatures = computeAudioFeatures(recFreqSamples);
document.getElementById("liveTranscriptText").innerHTML =
(recTranscriptText.trim() ? '<div style="margin-bottom:8px;color:var(--text)">' + escapeHtml(recTranscriptText) + '</div>' : '') +
'<div style="font-size:10px;color:var(--text3);border-top:1px solid var(--border);padding-top:8px">' +
'<b>Audio Analysis:</b> dominant_freq=' + audioFeatures.dominant_freq + 'Hz' +
' | spectral_centroid=' + audioFeatures.spectral_centroid +
' | rhythm_bpm=' + audioFeatures.estimated_bpm +
' | patterns=' + audioFeatures.patterns.length +
' | samples=' + audioFeatures.sample_count +
'</div>';
setEtlStage('transcribe', 'done');
// Upload audio + features + transcript, then generate
if (audioChunks.length > 0) {
const blob = new Blob(audioChunks, { type: "audio/webm" });
const reader = new FileReader();
reader.onloadend = () => {
const b64 = reader.result.split(",")[1];
if (b64) {
fetch("/audio/store", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ audio_b64: b64, label: "standalone", transcript: recTranscriptText, audio_features: audioFeatures })
}).then(r => r.json()).then(data => {
audioChunks = [];
document.getElementById("recStatus").textContent = "Analyzed";
document.getElementById("audioStatus").textContent = "analyzed";
loadAudioList();
// Always trigger generation β€” with or without speech
const payload = {
transcript: recTranscriptText.trim() || "[no speech detected β€” environmental audio with patterns: " + audioFeatures.patterns.length + " repeating cycles, dominant freq " + audioFeatures.dominant_freq + "Hz, estimated BPM " + audioFeatures.estimated_bpm + "]",
audio_file: data.audio_file,
audio_features: audioFeatures,
mode: document.getElementById('mode')?.value || 'continuous_code'
};
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "transcript", text: payload.transcript, audio_features: audioFeatures }));
setEtlStage('observe', 'active');
setStatus("Audio + features sent to observer LLM", "dot-thinking");
} else {
triggerGenerateFromAudio(payload.transcript, data.audio_file, audioFeatures);
}
}).catch(() => { });
}
};
reader.readAsDataURL(blob);
}
};
mediaRecorder.start(1000); // timeslice = 1s chunks
// Speech recognition with auto-restart (Chrome stops after ~60s)
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SR) {
recRecognition = new SR();
recRecognition.lang = "en-US";
recRecognition.continuous = true;
recRecognition.interimResults = true;
recRecognition.onresult = (event) => {
let interim = "", final = "";
for (let i = event.resultIndex; i < event.results.length; i++) {
if (event.results[i].isFinal) final += event.results[i][0].transcript + " ";
else interim += event.results[i][0].transcript;
}
recTranscriptText += final;
const display = recTranscriptText + (interim ? '<span style="color:var(--text3)">' + escapeHtml(interim) + '</span>' : '');
document.getElementById("liveTranscriptText").innerHTML = display || "Listening...";
if (final.trim()) setEtlStage('transcribe', 'active');
};
recRecognition.onerror = (e) => {
console.warn('SR error:', e.error);
if (e.error === 'not-allowed' || e.error === 'service-not-allowed') {
document.getElementById("liveTranscriptText").innerHTML =
'<span style="color:var(--amber)">Speech transcription unavailable. Audio analysis running β€” extracting frequencies, rhythms, and environmental patterns.</span>';
}
};
recRecognition.onend = () => {
if (mediaRecorder && mediaRecorder.state === 'recording') {
try { recRecognition.start(); } catch (e) { }
}
};
try { recRecognition.start(); } catch (e) { console.warn('SR start failed:', e); }
} else {
document.getElementById("liveTranscriptText").innerHTML =
'<span style="color:var(--amber)">SpeechRecognition not available. Audio analysis running β€” extracting frequencies, rhythms, and patterns from environmental audio.</span>';
}
document.getElementById("recButton").classList.add("recording");
document.getElementById("recStatus").textContent = "Recording + analyzing...";
document.getElementById("audioStatus").textContent = "recording";
document.getElementById("liveTranscript").style.display = "block";
document.getElementById("liveTranscriptText").textContent = "Listening...";
setEtlStage('capture', 'active');
recSeconds = 0;
recTimer = setInterval(() => {
recSeconds++;
const m = Math.floor(recSeconds / 60).toString().padStart(2, '0');
const s = (recSeconds % 60).toString().padStart(2, '0');
document.getElementById("recTime").textContent = m + ':' + s;
}, 1000);
// Real waveform from frequency data + sample for pattern analysis
const wf = document.getElementById("waveform");
wf.innerHTML = Array.from({ length: 40 }, () => '<div class="wave-bar" style="height:4px"></div>').join("");
waveformTimer = setInterval(() => {
if (recAnalyser && recFreqData) {
recAnalyser.getByteFrequencyData(recFreqData);
if (recSeconds % 2 === 0 && recSeconds > 0) {
recFreqSamples.push(Array.from(recFreqData.slice(0, 128)));
}
const bars = wf.querySelectorAll(".wave-bar");
const step = Math.floor(recFreqData.length / bars.length);
bars.forEach((bar, i) => {
const val = recFreqData[i * step] || 0;
bar.style.height = Math.max(4, (val / 255) * 40) + "px";
});
}
}, 50);
setStatus("Audio recording + analysis started", "dot-active");
} catch (e) {
setStatus("Audio recording error: " + e.message, "dot-error");
}
}
// Compute audio features from collected frequency samples
function computeAudioFeatures(samples) {
if (!samples || samples.length === 0) {
return { dominant_freq: 0, spectral_centroid: 0, estimated_bpm: 0, patterns: [], sample_count: 0, avg_energy: 0 };
}
const nBins = samples[0].length;
const avgSpectrum = new Array(nBins).fill(0);
for (const s of samples) for (let i = 0; i < nBins; i++) avgSpectrum[i] += s[i];
for (let i = 0; i < nBins; i++) avgSpectrum[i] /= samples.length;
let maxBin = 0, maxVal = 0;
for (let i = 0; i < nBins; i++) { if (avgSpectrum[i] > maxVal) { maxVal = avgSpectrum[i]; maxBin = i; } }
const dominantFreq = Math.round(maxBin * 44100 / 2048);
let sumWeighted = 0, sumMag = 0;
for (let i = 0; i < nBins; i++) {
const freq = i * 44100 / 2048;
sumWeighted += freq * avgSpectrum[i];
sumMag += avgSpectrum[i];
}
const spectralCentroid = sumMag > 0 ? Math.round(sumWeighted / sumMag) : 0;
const energies = samples.map(s => s.reduce((a, b) => a + b, 0) / s.length);
let bpm = 0;
if (energies.length > 4) {
const peaks = [];
for (let i = 1; i < energies.length - 1; i++) {
if (energies[i] > energies[i - 1] && energies[i] > energies[i + 1] && energies[i] > 50) peaks.push(i);
}
if (peaks.length > 1) {
const intervals = [];
for (let i = 1; i < peaks.length; i++) intervals.push(peaks[i] - peaks[i - 1]);
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
bpm = avgInterval > 0 ? Math.round(60 / (avgInterval * 2)) : 0;
}
}
// Detect repeating patterns via autocorrelation
const patterns = [];
if (samples.length > 6) {
for (let lag = 2; lag < Math.min(samples.length / 2, 10); lag++) {
let corr = 0, count = 0;
for (let i = 0; i < samples.length - lag; i++) {
for (let j = 0; j < nBins; j++) { corr += Math.abs(samples[i][j] - samples[i + lag][j]); count++; }
}
const avgDiff = corr / count;
if (avgDiff < 15) patterns.push({ lag_seconds: lag * 2, correlation: Math.round((1 - avgDiff / 128) * 100) / 100 });
}
}
return { dominant_freq: dominantFreq, spectral_centroid: spectralCentroid, estimated_bpm: bpm, patterns, sample_count: samples.length, avg_energy: Math.round(energies.reduce((a, b) => a + b, 0) / energies.length) };
}
// TTS β€” speak observer reasoning aloud
let ttsEnabled = true;
let ttsVoice = null;
function speakText(text) {
if (!ttsEnabled || !text || !('speechSynthesis' in window)) return;
window.speechSynthesis.cancel();
if (!ttsVoice) {
const voices = window.speechSynthesis.getVoices();
ttsVoice = voices.find(v => v.lang.startsWith('en') && v.name.includes('Google')) || voices.find(v => v.lang.startsWith('en')) || voices[0];
}
const sections = parseSections(text);
let speak = sections.REASONING || sections.INFERRED_INTENT || text.substring(0, 500);
speak = speak.replace(/```[\s\S]*?```/g, ' code omitted ').replace(/[#*\-]/g, ' ').replace(/\s+/g, ' ').trim();
if (speak.length > 600) speak = speak.substring(0, 600) + '. That is the key finding.';
const utter = new SpeechSynthesisUtterance(speak);
if (ttsVoice) utter.voice = ttsVoice;
utter.rate = 1.1; utter.pitch = 0.9;
window.speechSynthesis.speak(utter);
}
if ('speechSynthesis' in window) window.speechSynthesis.onvoiceschanged = () => { ttsVoice = null; };
function toggleTTS() {
ttsEnabled = !ttsEnabled;
const btn = document.getElementById('ttsBtn');
if (ttsEnabled) {
btn.style.borderColor = 'var(--accent)';
btn.style.color = 'var(--accent)';
btn.innerHTML = btn.innerHTML.replace('TTS: Off', 'TTS: On');
} else {
btn.style.borderColor = 'var(--text3)';
btn.style.color = 'var(--text3)';
btn.innerHTML = btn.innerHTML.replace('TTS: On', 'TTS: Off');
window.speechSynthesis.cancel();
}
}
// Trigger code generation from audio transcript when WS not connected
async function triggerGenerateFromAudio(transcript, audioFile, audioFeatures) {
setEtlStage('observe', 'active');
setStatus("Sending audio + analysis to LLM...", "dot-thinking");
try {
const resp = await fetch('/audio/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transcript: transcript, audio_file: audioFile, audio_features: audioFeatures, mode: document.getElementById('mode')?.value || 'continuous_code' })
});
const data = await resp.json();
if (data.patch_output) {
setEtlStage('observe', 'done');
setEtlStage('build', 'done');
setEtlStage('receipt', 'done');
addPatchEntry(data.patch_output, data.patch_hash, data.receipt, undefined);
patchCount++;
document.getElementById("patchCount").textContent = patchCount + " patch" + (patchCount !== 1 ? "es" : "");
setStatus("Code patch generated from audio", "dot-active");
speakText(data.patch_output);
// Store artifact
const sections = parseSections(data.patch_output);
const codeText = (sections.CODE || "").replace(/^```python\s*/i, "").replace(/^```\s*/, "").replace(/```$/, "").trim();
if (codeText && data.patch_hash) {
fetch("/store-artifact?patch_hash=" + data.patch_hash + "&code=" + encodeURIComponent(codeText) + "&reasoning=" + encodeURIComponent(sections.REASONING || "") + "&evidence=" + encodeURIComponent(sections.EVIDENCE || "") + "&observer_output=" + encodeURIComponent(data.patch_output), { method: "POST" }).catch(() => { });
// Link audio to this artifact
if (audioChunks.length > 0) {
currentAudioHash = data.patch_hash;
finishAudioUpload(data.patch_hash);
}
if (document.getElementById("autoRun") && document.getElementById("autoRun").checked) {
setTimeout(() => runCode(data.patch_hash, null), 800);
}
}
} else if (data.error) {
setStatus("LLM error: " + data.error, "dot-error");
}
} catch (e) {
setStatus("Generate error: " + e.message, "dot-error");
}
}
// Load recorded audio files from server
async function loadAudioList() {
try {
const resp = await fetch('/audio/list');
const data = await resp.json();
const el = document.getElementById('audioList');
if (data.total === 0) {
el.innerHTML = '<div style="font-size:11px;color:var(--text3);padding:8px;font-family:var(--mono)">No recordings yet.</div>';
return;
}
el.innerHTML = data.files.map(f => {
const sizeKB = (f.size_bytes / 1024).toFixed(1);
const date = new Date(f.modified * 1000).toLocaleTimeString();
return '<div style="display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--panel2);border:1px solid var(--border);border-radius:8px">' +
'<span style="font-size:16px">🎡</span>' +
'<div style="flex:1;min-width:0">' +
'<div style="font-size:11px;font-family:var(--mono);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + f.filename + '</div>' +
'<div style="font-size:9px;color:var(--text3)">' + sizeKB + 'KB Β· ' + date + '</div>' +
'</div>' +
'<audio controls preload="none" src="/audio/' + f.filename + '" style="height:28px;width:200px" />' +
'<a href="/audio/' + f.filename + '" download style="font-size:11px;color:var(--accent);text-decoration:none">Download</a>' +
'</div>';
}).join('');
} catch (e) {
document.getElementById('audioList').innerHTML = '<div style="font-size:11px;color:var(--red);padding:8px">Error: ' + escapeHtml(e.message) + '</div>';
}
}
function finishAudioUpload(patchHash) {
if (audioChunks.length === 0) return;
const blob = new Blob(audioChunks, { type: "audio/webm" });
const reader = new FileReader();
reader.onloadend = () => {
const b64 = reader.result.split(",")[1];
if (b64) {
fetch("/audio/store?patch_hash=" + patchHash + "&audio_b64=" + encodeURIComponent(b64), { method: "POST" })
.then(() => {
audioChunks = [];
document.getElementById("recStatus").textContent = "Linked to artifact";
document.getElementById("audioStatus").textContent = "stored";
}).catch(() => { });
}
};
reader.readAsDataURL(blob);
}
// ── SPEAKER ENTRIES ──
function addSpeakerEntry(speaker, chars, chunkId) {
const ph = speakerLedger.querySelector(".empty-state");
if (ph) ph.remove();
const div = document.createElement("div");
div.className = "speaker-entry speaker-" + speaker;
div.innerHTML = '<div class="label">' + speaker + " &middot; " + chars + " chars &middot; " + chunkId + "</div>";
speakerLedger.prepend(div);
}
function updateSpeakerLedger(state) {
const speakers = (state.audio && state.audio.speakers) || {};
speakerLedger.innerHTML = "";
for (const [spk, info] of Object.entries(speakers)) {
const div = document.createElement("div");
div.className = "speaker-entry speaker-" + spk;
div.innerHTML = '<div class="label">' + spk + " &middot; " + info.chunk_count + " chunks</div>" +
'<div class="text">' + escapeHtml((info.transcript || "").slice(-300)) + "</div>";
speakerLedger.appendChild(div);
}
if (!speakerLedger.children.length) speakerLedger.innerHTML = '<div class="empty-state">No audio yet.</div>';
}
// ── OBSERVER ──
let observerHistory = [];
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;
}
function renderObserverTrace(output, novelty) {
const ph = observerPane.querySelector(".empty-state");
if (ph) ph.remove();
const sections = parseSections(output);
const ts = new Date().toLocaleTimeString();
observerHistory.unshift({ time: ts, output, novelty });
if (observerHistory.length > 20) observerHistory.pop();
let html = '<div class="observer-trace">';
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">';
html += '<span style="color:var(--accent);font-size:11px;font-weight:600">\u258C ' + ts + '</span>';
html += '<span style="color:var(--text3);font-size:10px">novelty: ' + novelty.toFixed(3) + ' \u00b7 ' + observerHistory.length + ' observations</span>';
html += '</div>';
if (sections.REASONING) { html += '<div class="section-header">\u258C REASONING</div><div class="reasoning">' + escapeHtml(sections.REASONING) + "</div>"; }
if (sections.SCENE) { html += '<div class="section-header">\u258C SCENE</div>' + escapeHtml(sections.SCENE) + "\n"; }
if (sections.INFERRED_INTENT || sections.INTENT) { html += '<div class="section-header">\u258C INFERRED INTENT</div><div class="intent">' + escapeHtml(sections.INFERRED_INTENT || sections.INTENT) + "</div>\n"; }
if (sections.SIGNALS) { html += '<div class="section-header">\u258C SIGNALS</div>' + escapeHtml(sections.SIGNALS) + "\n"; }
if (sections.CANDIDATE_TASK) { html += '<div class="section-header">\u258C CANDIDATE TASK</div>' + escapeHtml(sections.CANDIDATE_TASK) + "\n"; }
if (sections.UNCERTAINTY) { html += '<div class="section-header">\u258C UNCERTAINTY</div>' + escapeHtml(sections.UNCERTAINTY) + "\n"; }
if (!sections.REASONING && !sections.SCENE) html += escapeHtml(output);
html += "</div>";
observerPane.innerHTML = html;
}
// ── CODE PATCHES ──
function highlightCode(code) {
let html = escapeHtml(code);
html = html.replace(/(#[^\n]*)/g, '<span class="com">$1</span>');
html = html.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g, '<span class="str">$1</span>');
const kws = ['def', 'class', 'import', 'from', 'return', 'if', 'else', 'elif', 'for', 'while', 'try', 'except', 'finally', 'with', 'as', 'async', 'await', 'yield', 'lambda', 'pass', 'break', 'continue', 'raise', 'in', 'not', 'and', 'or', 'is', 'None', 'True', 'False'];
for (const kw of kws) html = html.replace(new RegExp("\\b(" + kw + ")\\b", "g"), '<span class="kw">$1</span>');
html = html.replace(/\b(\d+\.?\d*)\b/g, '<span class="num">$1</span>');
html = html.replace(/(\w+)(\()/g, '<span class="fn">$1</span>$2');
return html;
}
function addPatchEntry(output, hash, receipt, qvd) {
const ph = patchStream.querySelector(".empty-state");
if (ph) ph.remove();
const sections = parseSections(output);
const ts = new Date().toLocaleTimeString();
const filename = "patch_" + hash.slice(0, 8) + ".py";
let html = '<div class="ide-patch">';
html += '<div class="ide-titlebar">';
html += '<div class="ide-dots"><div class="ide-dot r"></div><div class="ide-dot y"></div><div class="ide-dot g"></div></div>';
html += '<div class="ide-filename">' + filename + "</div>";
html += '<div class="ide-meta"><span>' + ts + "</span>";
if (qvd !== undefined) html += "<span>QVD " + qvd.toFixed(3) + "</span>";
html += "<span>hash " + hash.slice(0, 12) + "</span></div>";
const codeText = (sections.CODE || "").replace(/^```python\s*/i, "").replace(/^```\s*/, "").replace(/```$/, "").trim();
if (codeText) {
html += '<button class="ide-run-btn" onclick="runCode(\'' + hash + '\', this)">\u25B6 Run</button>';
html += '<a class="ide-artifact-link" href="/artifact/' + hash + '" target="_blank">Artifact</a>';
}
html += "</div><div class=\"ide-body\">";
if (sections.EVIDENCE) {
html += '<div class="ide-section"><div class="ide-section-label label-evidence">\u258C Evidence</div><ul class="ide-evidence">';
for (const line of sections.EVIDENCE.split("\n").filter(l => l.trim())) html += "<li>" + escapeHtml(line.replace(/^[-\u2022*]\s*/, "")) + "</li>";
html += "</ul></div>";
}
if (sections.REASONING) {
html += '<div class="ide-section"><div class="ide-section-label label-reasoning">\u258C Reasoning</div><div class="ide-reasoning">' + escapeHtml(sections.REASONING) + "</div></div>";
}
if (sections.CODE) {
html += '<div class="ide-section" style="padding:0"><div class="ide-section-label label-code" style="padding:10px 14px 0">\u258C Code</div><pre class="ide-code">' + highlightCode(codeText) + "</pre></div>";
}
if (codeText) {
html += '<div class="ide-terminal" id="term_' + hash.slice(0, 12) + '"><div class="term-meta">Click \u25B6 Run to execute</div></div>';
}
if (sections.TEST) {
html += '<div class="ide-section ide-test"><div class="ide-section-label label-test" style="margin:0">\u258C Test</div><span class="prompt">$</span><span>' + escapeHtml(sections.TEST.trim()) + "</span></div>";
}
if (sections.ATTRIBUTION) {
html += '<div class="ide-section"><div class="ide-section-label" style="color:var(--text3)">\u258C Attribution</div><div class="ide-evidence">' + escapeHtml(sections.ATTRIBUTION) + "</div></div>";
}
if (!sections.EVIDENCE && !sections.REASONING && !sections.CODE) {
html += '<div class="ide-section"><pre class="ide-code">' + escapeHtml(output) + "</pre></div>";
}
html += "</div></div>";
const wrapper = document.createElement("div");
wrapper.innerHTML = html;
patchStream.prepend(wrapper.firstChild);
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
async function runCode(patchHash, btn) {
const termId = 'term_' + patchHash.slice(0, 12);
const term = document.getElementById(termId);
if (!term) return;
if (btn) { btn.disabled = true; btn.textContent = 'Running...'; }
term.innerHTML = '<div class="term-meta">Executing...</div>';
try {
const resp = await fetch('/run?patch_hash=' + patchHash, { method: 'POST' });
const data = await resp.json();
let html = '';
if (data.stdout) { html += '<div class="term-meta">exit_code: ' + data.exit_code + '</div><div class="term-stdout">' + escapeHtml(data.stdout) + '</div>'; }
if (data.stderr) { html += '<div class="term-stderr">' + escapeHtml(data.stderr) + '</div>'; }
if (!data.stdout && !data.stderr) { html += '<div class="term-meta">exit_code: ' + data.exit_code + ' (no output)</div>'; }
if (data.exit_code !== 0 && data.exit_code !== undefined) {
html += '<button class="ide-run-btn" style="background:rgba(251,191,36,.15);color:var(--amber);border-color:rgba(251,191,36,.3);margin-top:6px" onclick="debugCode(\'' + patchHash + '\', this)">\u2691 Debug &amp; Fix</button>';
}
term.innerHTML = html;
} catch (e) { term.innerHTML = '<div class="term-stderr">Error: ' + escapeHtml(e.message) + '</div>'; }
if (btn) { btn.disabled = false; btn.textContent = '\u25B6 Run'; }
}
async function debugCode(patchHash, btn) {
if (btn) { btn.disabled = true; btn.textContent = 'Debugging...'; }
const termId = 'term_' + patchHash.slice(0, 12);
const term = document.getElementById(termId);
try {
const resp = await fetch('/debug?patch_hash=' + patchHash, { method: 'POST' });
const data = await resp.json();
if (data.error) {
if (term) term.innerHTML += '<div class="term-stderr">Debug error: ' + escapeHtml(data.error) + '</div>';
} else {
addPatchEntry(data.patch_output, data.patch_hash, {}, undefined);
patchCount++;
document.getElementById("patchCount").textContent = patchCount + " patch" + (patchCount !== 1 ? "es" : "");
if (term) term.innerHTML += '<div class="term-meta" style="color:var(--amber)">\u2713 Fixed! New artifact: ' + data.patch_hash.slice(0, 12) + '</div>';
if (document.getElementById("autoRun") && document.getElementById("autoRun").checked) {
setTimeout(() => runCode(data.patch_hash, null), 500);
}
}
} catch (e) {
if (term) term.innerHTML += '<div class="term-stderr">Error: ' + escapeHtml(e.message) + '</div>';
}
if (btn) { btn.disabled = false; btn.textContent = '\u2691 Debug &amp; Fix'; }
}
// ── TERMINAL ──
let currentTermSession = null;
let termPollTimer = null;
async function createTerminalSession() {
try {
const resp = await fetch("/terminal/create", { method: "POST" });
const data = await resp.json();
currentTermSession = data.session_id;
document.getElementById("termInput").disabled = false;
document.getElementById("termStatus").textContent = data.session_id.slice(0, 12);
document.getElementById("termOutput").innerHTML = '<div class="term-line term-info">CSC Engine Terminal β€” Session: ' + data.session_id + '</div><div class="term-line term-info">Kali Linux β€” 31 languages available</div><div class="term-line"></div>';
document.getElementById("termInput").focus();
startTermPolling();
} catch (e) {
document.getElementById("termOutput").innerHTML = '<div class="term-line term-err">Error: ' + escapeHtml(e.message) + '</div>';
}
}
async function termExec(cmd) {
if (!currentTermSession) { await createTerminalSession(); }
if (!currentTermSession) return;
const outEl = document.getElementById("termOutput");
outEl.innerHTML += '<div class="term-line"><span class="term-prompt">csc@kali:~$</span> ' + escapeHtml(cmd) + '</div>';
outEl.scrollTop = outEl.scrollHeight;
termHistory.push(cmd);
updateTermHistory();
try {
const resp = await fetch("/terminal/" + currentTermSession + "/exec?command=" + encodeURIComponent(cmd), { method: "POST" });
const data = await resp.json();
if (data.error) outEl.innerHTML += '<div class="term-line term-err">' + escapeHtml(data.error) + '</div>';
else if (data.output) outEl.innerHTML += '<div class="term-line term-out">' + escapeHtml(data.output) + '</div>';
outEl.scrollTop = outEl.scrollHeight;
updateTermSide();
} catch (e) {
outEl.innerHTML += '<div class="term-line term-err">' + escapeHtml(e.message) + '</div>';
}
}
async function handleTermInput(event) {
if (event.key !== "Enter") return;
const input = document.getElementById("termInput");
const cmd = input.value.trim();
if (!cmd) return;
input.value = "";
await termExec(cmd);
}
function updateTermHistory() {
const panel = document.getElementById("termHistoryPanel");
const list = document.getElementById("termHistoryList");
if (termHistory.length === 0) { panel.style.display = "none"; return; }
panel.style.display = "block";
list.innerHTML = termHistory.slice(-20).reverse().map(cmd =>
'<div class="terminal-history-item" onclick="termExec(\'' + escapeHtml(cmd).replace(/'/g, "\\'") + '\')">$ ' + escapeHtml(cmd) + '</div>'
).join("");
}
function startTermPolling() {
if (termPollTimer) clearInterval(termPollTimer);
termPollTimer = setInterval(pollTerminalOutput, 3000);
}
async function pollTerminalOutput() {
if (!currentTermSession) return;
try {
const resp = await fetch("/terminal/" + currentTermSession + "/output");
const data = await resp.json();
if (data.output) {
const outEl = document.getElementById("termOutput");
const existing = outEl.querySelector(".term-poll-out");
if (existing) existing.remove();
outEl.innerHTML += '<div class="term-line term-out term-poll-out">' + escapeHtml(data.output.slice(-2000)) + '</div>';
outEl.scrollTop = outEl.scrollHeight;
}
document.getElementById("tsStatus").textContent = data.is_alive ? "alive" : "dead";
document.getElementById("tsLines").textContent = data.line_count || 0;
} catch (e) { }
}
async function updateTermSide() {
if (!currentTermSession) return;
try {
const resp = await fetch("/terminal/" + currentTermSession + "/status");
const s = await resp.json();
document.getElementById("tsStatus").textContent = s.alive ? "alive" : "dead";
document.getElementById("tsUptime").textContent = s.uptime_s + "s";
document.getElementById("tsLines").textContent = s.buffer_lines;
document.getElementById("tsLastCmd").textContent = (s.last_command || "β€”").slice(0, 20);
} catch (e) { }
}
async function killTerminalSession() {
if (!currentTermSession) return;
try {
await fetch("/terminal/" + currentTermSession + "/kill", { method: "POST" });
document.getElementById("termOutput").innerHTML += '<div class="term-line term-err">Session killed</div>';
currentTermSession = null;
document.getElementById("termInput").disabled = true;
document.getElementById("termStatus").textContent = "no session";
if (termPollTimer) { clearInterval(termPollTimer); termPollTimer = null; }
} catch (e) { }
}
// ── BUTTONS ──
document.getElementById("startBtn").onclick = () => startLive().catch(e => setStatus(e.message, "dot-error"));
document.getElementById("stopBtn").onclick = stopLive;
document.getElementById("forceBtn").onclick = () => sendFrame(true);
// ── COMMAND PALETTE ──
const commands = [
{ icon: "🏠", label: "Home", desc: "Landing page", action: () => switchView('landing') },
{ icon: "β–Ά", label: "Start Engine", desc: "Begin live capture", action: () => startLive().catch(e => setStatus(e.message, "dot-error")) },
{ icon: "β– ", label: "Stop Engine", desc: "Stop capture", action: stopLive },
{ icon: "⚑", label: "Force Generate", desc: "Force code generation", action: () => sendFrame(true) },
{ icon: "🎀", label: "Start Speech", desc: "Begin speech recognition", action: startSpeech },
{ icon: "πŸ”΄", label: "Record Audio", desc: "Toggle audio recording", action: toggleRecording },
{ icon: "πŸ–₯", label: "Terminal", desc: "Open terminal view", action: () => switchView('terminal') },
{ icon: "πŸ“–", label: "Lab Diary", desc: "View all artifacts", action: () => window.open("/diary", "_blank") },
{ icon: "πŸ“", label: "API Help", desc: "List all endpoints", action: () => window.open("/api/help", "_blank") },
{ icon: "πŸ”Œ", label: "GPT Actions Schema", desc: "ChatGPT integration", action: () => window.open("/gpt-actions.json", "_blank") },
{ icon: "πŸ₯", label: "Health Check", desc: "Check server status", action: () => fetch("/health").then(r => r.json()).then(d => setStatus(JSON.stringify(d).slice(0, 80), "dot-active")) },
{ icon: "πŸ“Š", label: "Metrics", desc: "View Prometheus metrics", action: () => window.open("/metrics", "_blank") },
{ icon: "πŸ”", label: "Deep Health", desc: "Detailed health check", action: () => window.open("/health/detailed", "_blank") },
{ icon: "πŸ“œ", label: "Audit Log", desc: "View tamper-evident audit trail", action: () => window.open("/audit/log", "_blank") },
];
let cmdSelectedIndex = 0;
function openCmdPalette() {
document.getElementById("cmdOverlay").classList.add("open");
document.getElementById("cmdPalette").classList.add("open");
const input = document.getElementById("cmdInput");
input.value = ""; input.focus();
cmdSelectedIndex = 0;
renderCmds(commands);
}
function closeCmdPalette() {
document.getElementById("cmdOverlay").classList.remove("open");
document.getElementById("cmdPalette").classList.remove("open");
}
function filterCmds() {
const q = document.getElementById("cmdInput").value.toLowerCase();
const filtered = commands.filter(c => c.label.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q));
cmdSelectedIndex = 0;
renderCmds(filtered);
}
function renderCmds(cmds) {
const el = document.getElementById("cmdResults");
el.innerHTML = cmds.map((c, i) =>
'<div class="cmd-item' + (i === cmdSelectedIndex ? ' selected' : '') + '" onclick="execCmd(' + commands.indexOf(c) + ')">' +
'<span class="cmd-icon">' + c.icon + '</span><span class="cmd-label">' + c.label + '</span>' +
'<span class="cmd-desc">' + c.desc + '</span></div>'
).join("");
}
function execCmd(idx) { closeCmdPalette(); if (commands[idx]) commands[idx].action(); }
function handleCmdKey(event) {
const q = document.getElementById("cmdInput").value.toLowerCase();
const filtered = commands.filter(c => c.label.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q));
if (event.key === "ArrowDown") { event.preventDefault(); cmdSelectedIndex = Math.min(cmdSelectedIndex + 1, filtered.length - 1); renderCmds(filtered); }
else if (event.key === "ArrowUp") { event.preventDefault(); cmdSelectedIndex = Math.max(cmdSelectedIndex - 1, 0); renderCmds(filtered); }
else if (event.key === "Enter") { event.preventDefault(); if (filtered[cmdSelectedIndex]) execCmd(commands.indexOf(filtered[cmdSelectedIndex])); }
else if (event.key === "Escape") { closeCmdPalette(); }
}
// ── KEYBOARD ──
document.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); openCmdPalette(); }
if (e.key === "Escape") { closeCmdPalette(); }
});
</script>
</body>
</html>