Spaces:
Paused
Paused
| <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> | |
| · 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 + " · " + chars + " chars · " + 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 + " · " + 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 & 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 & 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> |