Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>SocraticEnv β Live Dashboard</title> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Segoe UI', system-ui, sans-serif; | |
| color: #e6edf3; min-height: 100vh; | |
| background: #050B14; | |
| position: relative; | |
| overflow-x: hidden; | |
| } | |
| body::before { | |
| content: ''; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: -2; | |
| background: | |
| radial-gradient(circle at 20% 30%, rgba(0, 243, 255, 0.05) 0%, transparent 40%), | |
| radial-gradient(circle at 80% 70%, rgba(10, 25, 47, 0.8) 0%, transparent 50%), | |
| radial-gradient(circle at 50% 50%, rgba(5, 11, 20, 1) 0%, #050B14 100%); | |
| animation: pulseBg 10s ease-in-out infinite alternate; | |
| } | |
| body::after { | |
| content: ''; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: -1; | |
| pointer-events: none; | |
| background: repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15) 1px, transparent 1px, transparent 2px); | |
| } | |
| @keyframes pulseBg { | |
| 0% { opacity: 0.8; transform: scale(1); } | |
| 100% { opacity: 1; transform: scale(1.05); } | |
| } | |
| .header { | |
| background: #161b22; border-bottom: 1px solid #30363d; | |
| padding: 16px 32px; display: flex; align-items: center; | |
| justify-content: space-between; | |
| } | |
| .header-left { display: flex; align-items: center; gap: 12px; } | |
| .logo { | |
| width: 36px; height: 36px; | |
| background: linear-gradient(135deg, #7c3aed, #a855f7); | |
| border-radius: 8px; display: flex; align-items: center; | |
| justify-content: center; font-size: 18px; | |
| } | |
| .header h1 { font-size: 18px; font-weight: 600; color: #e6edf3; } | |
| .header p { font-size: 12px; color: #8b949e; margin-top: 2px; } | |
| .header-right { display: flex; align-items: center; gap: 10px; } | |
| .nav-link { | |
| padding: 6px 14px; border-radius: 8px; font-size: 12px; | |
| font-weight: 600; text-decoration: none; border: 1px solid #30363d; | |
| color: #8b949e; background: #21262d; transition: all 0.2s; | |
| } | |
| .nav-link:hover { color: #e6edf3; border-color: #7c3aed; } | |
| .nav-link.active { color: #a855f7; border-color: #7c3aed; background: #13111e; } | |
| .status-badge { | |
| display: flex; align-items: center; gap: 6px; | |
| background: #1a2332; border: 1px solid #30363d; | |
| border-radius: 20px; padding: 6px 14px; | |
| font-size: 12px; color: #8b949e; | |
| } | |
| .status-dot { | |
| width: 8px; height: 8px; border-radius: 50%; | |
| background: #3fb950; box-shadow: 0 0 6px #3fb950; | |
| animation: pulse 2s infinite; | |
| } | |
| .status-dot.offline { background: #f85149; box-shadow: 0 0 6px #f85149; animation: none; } | |
| @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.5} } | |
| .container { | |
| display: grid; grid-template-columns: 300px 1fr; | |
| height: calc(100vh - 69px); | |
| transition: grid-template-columns 0.3s ease; | |
| } | |
| .container.devtools-open { | |
| grid-template-columns: 300px 1fr 340px; | |
| } | |
| .sidebar { | |
| background: #161b22; border-right: 1px solid #30363d; | |
| padding: 20px; overflow-y: auto; | |
| } | |
| .sidebar-section { margin-bottom: 24px; } | |
| .sidebar-title { | |
| font-size: 11px; font-weight: 600; color: #8b949e; | |
| letter-spacing: 1px; text-transform: uppercase; margin-bottom: 12px; | |
| } | |
| .task-card { | |
| background: #0d1117; border: 1px solid #30363d; | |
| border-radius: 10px; padding: 14px; margin-bottom: 8px; | |
| cursor: pointer; transition: all 0.2s; | |
| } | |
| .task-card:hover { border-color: #7c3aed; background: #13111e; } | |
| .task-card.active { | |
| border-color: #7c3aed; background: #13111e; | |
| box-shadow: 0 0 0 1px #7c3aed22; | |
| } | |
| .task-header { | |
| display: flex; align-items: center; | |
| justify-content: space-between; margin-bottom: 6px; | |
| } | |
| .task-name { font-size: 13px; font-weight: 600; color: #e6edf3; } | |
| .difficulty { | |
| font-size: 10px; font-weight: 600; padding: 2px 8px; | |
| border-radius: 10px; text-transform: uppercase; letter-spacing: 0.5px; | |
| } | |
| .easy { background: #1a3a2a; color: #3fb950; border: 1px solid #3fb95040; } | |
| .medium { background: #332d1a; color: #d29922; border: 1px solid #d2992240; } | |
| .hard { background: #3a1a1a; color: #f85149; border: 1px solid #f8514940; } | |
| .task-desc { font-size: 11px; color: #8b949e; line-height: 1.5; } | |
| .score-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } | |
| .score-card { | |
| background: #0d1117; border: 1px solid #30363d; | |
| border-radius: 8px; padding: 12px; text-align: center; | |
| } | |
| .score-value { font-size: 22px; font-weight: 700; color: #7c3aed; } | |
| .score-label { font-size: 10px; color: #8b949e; margin-top: 2px; } | |
| .score-card.full { grid-column: 1 / 3; } | |
| .score-card.full .score-value { font-size: 28px; color: #a855f7; } | |
| .turn-track { display: flex; gap: 4px; margin-top: 4px; } | |
| .turn-dot { | |
| flex: 1; height: 4px; border-radius: 2px; | |
| background: #30363d; transition: background 0.3s; | |
| } | |
| .turn-dot.done { background: #7c3aed; } | |
| .turn-dot.current { background: #a855f7; animation: pulse 1s infinite; } | |
| /* NEW: Styling for the chart container */ | |
| .chart-container { | |
| background: #0d1117; border: 1px solid #30363d; | |
| border-radius: 8px; padding: 10px; margin-top: 8px; | |
| } | |
| .main { display: flex; flex-direction: column; overflow: hidden; } | |
| .controls { | |
| background: #161b22; border-bottom: 1px solid #30363d; | |
| padding: 14px 24px; display: flex; align-items: center; gap: 12px; | |
| } | |
| .btn { | |
| padding: 8px 18px; border-radius: 8px; font-size: 13px; | |
| font-weight: 600; border: none; cursor: pointer; | |
| transition: all 0.2s; display: flex; align-items: center; gap: 6px; | |
| } | |
| .btn-primary { background: #7c3aed; color: white; } | |
| .btn-primary:hover { background: #6d28d9; } | |
| .btn-primary:disabled { background: #3d2070; color: #8b6bb5; cursor: not-allowed; } | |
| .btn-secondary { background: #21262d; color: #e6edf3; border: 1px solid #30363d; } | |
| .btn-secondary:hover { background: #30363d; } | |
| .btn-danger { background: #3a1a1a; color: #f85149; border: 1px solid #f8514940; } | |
| .btn-danger:hover { background: #f8514920; } | |
| .controls-right { margin-left: auto; display: flex; align-items: center; gap: 10px; } | |
| .speed-label { font-size: 12px; color: #8b949e; } | |
| .speed-select { | |
| background: #21262d; border: 1px solid #30363d; | |
| color: #e6edf3; border-radius: 6px; padding: 5px 10px; font-size: 12px; | |
| } | |
| .dialogue-area { | |
| flex: 1; overflow-y: auto; padding: 24px; | |
| display: flex; flex-direction: column; gap: 16px; | |
| } | |
| .empty-state { | |
| flex: 1; display: flex; flex-direction: column; | |
| align-items: center; justify-content: center; | |
| gap: 12px; color: #8b949e; margin: auto; | |
| } | |
| .empty-icon { font-size: 48px; opacity: 0.4; } | |
| .empty-title { font-size: 16px; font-weight: 600; color: #8b949e; } | |
| .empty-sub { font-size: 13px; } | |
| .message { | |
| display: flex; gap: 12px; max-width: 85%; | |
| animation: fadeUp 0.3s ease; | |
| } | |
| @keyframes fadeUp { | |
| from { opacity:0; transform: translateY(8px); } | |
| to { opacity:1; transform: translateY(0); } | |
| } | |
| .message.tutor { align-self: flex-start; } | |
| .message.agent { align-self: flex-end; flex-direction: row-reverse; } | |
| .avatar { | |
| width: 36px; height: 36px; border-radius: 50%; | |
| display: flex; align-items: center; justify-content: center; | |
| font-size: 16px; flex-shrink: 0; margin-top: 2px; | |
| } | |
| .tutor .avatar { background: linear-gradient(135deg, #7c3aed, #a855f7); } | |
| .agent .avatar { background: linear-gradient(135deg, #0d9488, #14b8a6); } | |
| .bubble { | |
| padding: 12px 16px; border-radius: 12px; | |
| font-size: 14px; line-height: 1.6; max-width: 100%; | |
| } | |
| .tutor .bubble { | |
| background: #161b22; border: 1px solid #30363d; | |
| border-top-left-radius: 4px; color: #e6edf3; | |
| } | |
| .agent .bubble { | |
| background: #13111e; border: 1px solid #7c3aed40; | |
| border-top-right-radius: 4px; color: #e6edf3; | |
| } | |
| .bubble-meta { | |
| font-size: 11px; color: #8b949e; margin-top: 6px; | |
| display: flex; align-items: center; gap: 8px; | |
| } | |
| .agent .bubble-meta { justify-content: flex-end; } | |
| .reward-pill { | |
| display: inline-flex; align-items: center; gap: 4px; | |
| padding: 2px 8px; border-radius: 10px; | |
| font-size: 11px; font-weight: 600; | |
| } | |
| .reward-high { background: #1a3a2a; color: #3fb950; } | |
| .reward-mid { background: #332d1a; color: #d29922; } | |
| .reward-low { background: #3a1a1a; color: #f85149; } | |
| .breakdown { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; } | |
| .breakdown-item { | |
| font-size: 10px; padding: 2px 7px; border-radius: 6px; | |
| background: #21262d; border: 1px solid #30363d; color: #8b949e; | |
| } | |
| .typing { display: flex; gap: 12px; align-self: flex-start; } | |
| .typing .avatar { background: linear-gradient(135deg, #0d9488, #14b8a6); } | |
| .typing-dots { | |
| background: #161b22; border: 1px solid #30363d; | |
| border-radius: 12px; border-top-left-radius: 4px; | |
| padding: 12px 16px; display: flex; gap: 4px; align-items: center; | |
| } | |
| .dot { | |
| width: 6px; height: 6px; border-radius: 50%; | |
| background: #8b949e; animation: bounce 1.2s infinite; | |
| } | |
| .dot:nth-child(2) { animation-delay: 0.2s; } | |
| .dot:nth-child(3) { animation-delay: 0.4s; } | |
| @keyframes bounce { | |
| 0%,60%,100%{transform:translateY(0)} 30%{transform:translateY(-6px)} | |
| } | |
| .input-area { | |
| background: #161b22; border-top: 1px solid #30363d; padding: 16px 24px; | |
| } | |
| .input-row { display: flex; gap: 10px; } | |
| .input-box { | |
| flex: 1; background: #0d1117; border: 1px solid #30363d; | |
| border-radius: 10px; padding: 10px 16px; color: #e6edf3; | |
| font-size: 14px; font-family: inherit; resize: none; | |
| transition: border 0.2s; min-height: 44px; max-height: 120px; | |
| } | |
| .input-box:focus { outline: none; border-color: #7c3aed; } | |
| .input-box::placeholder { color: #484f58; } | |
| .btn-send { | |
| background: #7c3aed; border: none; border-radius: 10px; | |
| color: white; padding: 10px 18px; cursor: pointer; | |
| font-size: 18px; transition: background 0.2s; align-self: flex-end; | |
| } | |
| .btn-send:hover { background: #6d28d9; } | |
| .btn-send:disabled { background: #3d2070; cursor: not-allowed; } | |
| .input-hint { | |
| font-size: 11px; color: #484f58; margin-top: 6px; | |
| display: flex; justify-content: space-between; | |
| } | |
| .autorun-banner { | |
| background: #13111e; border: 1px solid #7c3aed40; | |
| border-radius: 8px; padding: 8px 14px; font-size: 12px; | |
| color: #a855f7; display: none; align-items: center; | |
| gap: 8px; margin-bottom: 10px; | |
| } | |
| .autorun-banner.visible { display: flex; } | |
| .complete-banner { | |
| background: #1a3a2a; border: 1px solid #3fb95040; | |
| border-radius: 10px; padding: 16px 20px; | |
| display: flex; align-items: center; | |
| justify-content: space-between; animation: fadeUp 0.3s ease; | |
| } | |
| .complete-left { display: flex; align-items: center; gap: 12px; } | |
| .complete-icon { font-size: 24px; } | |
| .complete-title { font-size: 14px; font-weight: 600; color: #3fb950; } | |
| .complete-sub { font-size: 12px; color: #8b949e; margin-top: 2px; } | |
| .final-score { font-size: 28px; font-weight: 700; color: #3fb950; } | |
| .system-msg { | |
| text-align: center; font-size: 12px; color: #8b949e; | |
| padding: 8px 16px; background: #161b22; | |
| border: 1px solid #30363d; border-radius: 8px; | |
| align-self: center; | |
| } | |
| .system-msg.error { color: #f85149; border-color: #f8514940; background: #3a1a1a; } | |
| .system-msg.warning { color: #d29922; border-color: #d2992240; background: #332d1a; } | |
| ::-webkit-scrollbar { width: 4px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; } | |
| /* ββ Glass Box Inspector Panel βββββββββββββββββββββ */ | |
| .glassbox-panel { | |
| background: #0d1117; | |
| border-left: 1px solid #30363d; | |
| display: none; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| .container.devtools-open .glassbox-panel { | |
| display: flex; | |
| } | |
| .glassbox-header { | |
| background: #161b22; | |
| border-bottom: 1px solid #30363d; | |
| padding: 14px 18px; | |
| display: flex; align-items: center; justify-content: space-between; | |
| } | |
| .glassbox-header h3 { | |
| font-size: 13px; font-weight: 700; color: #e6edf3; | |
| display: flex; align-items: center; gap: 8px; | |
| } | |
| .glassbox-header h3 .badge { | |
| font-size: 9px; font-weight: 700; | |
| background: linear-gradient(135deg, #7c3aed, #a855f7); | |
| color: white; padding: 2px 7px; border-radius: 10px; | |
| letter-spacing: 0.5px; text-transform: uppercase; | |
| } | |
| .glassbox-body { | |
| flex: 1; overflow-y: auto; padding: 16px; | |
| display: flex; flex-direction: column; gap: 12px; | |
| } | |
| .glassbox-empty { | |
| flex: 1; display: flex; flex-direction: column; | |
| align-items: center; justify-content: center; | |
| gap: 8px; color: #484f58; font-size: 12px; | |
| } | |
| .glassbox-empty .icon { font-size: 32px; opacity: 0.3; } | |
| .gb-turn-card { | |
| background: #161b22; border: 1px solid #30363d; | |
| border-radius: 10px; overflow: hidden; | |
| animation: fadeUp 0.3s ease; | |
| } | |
| .gb-turn-header { | |
| padding: 10px 14px; display: flex; | |
| align-items: center; justify-content: space-between; | |
| border-bottom: 1px solid #21262d; | |
| } | |
| .gb-turn-label { | |
| font-size: 11px; font-weight: 700; color: #8b949e; | |
| letter-spacing: 0.5px; text-transform: uppercase; | |
| } | |
| .gb-turn-score { | |
| font-size: 13px; font-weight: 700; padding: 2px 10px; | |
| border-radius: 10px; | |
| } | |
| .gb-turn-score.high { background: #1a3a2a; color: #3fb950; } | |
| .gb-turn-score.mid { background: #332d1a; color: #d29922; } | |
| .gb-turn-score.low { background: #3a1a1a; color: #f85149; } | |
| .gb-breakdown-list { | |
| padding: 10px 14px; display: flex; | |
| flex-direction: column; gap: 6px; | |
| } | |
| .gb-row { | |
| display: flex; align-items: center; | |
| justify-content: space-between; | |
| padding: 5px 10px; border-radius: 6px; | |
| font-size: 12px; font-weight: 500; | |
| transition: background 0.15s; | |
| } | |
| .gb-row:hover { filter: brightness(1.2); } | |
| .gb-row.positive { | |
| background: #0d2818; border: 1px solid #1a3a2a; | |
| color: #3fb950; | |
| } | |
| .gb-row.negative { | |
| background: #2a0f0f; border: 1px solid #3a1a1a; | |
| color: #f85149; | |
| } | |
| .gb-row.neutral { | |
| background: #1a1d23; border: 1px solid #30363d; | |
| color: #8b949e; | |
| } | |
| .gb-key { | |
| font-family: 'SF Mono', 'Consolas', 'Monaco', monospace; | |
| font-size: 11px; | |
| } | |
| .gb-val { | |
| font-weight: 700; font-size: 12px; | |
| font-family: 'SF Mono', 'Consolas', 'Monaco', monospace; | |
| } | |
| .btn-devtools { | |
| background: #161b22; color: #8b949e; border: 1px solid #30363d; | |
| border-radius: 8px; padding: 8px 14px; font-size: 12px; | |
| font-weight: 600; cursor: pointer; transition: all 0.2s; | |
| display: flex; align-items: center; gap: 6px; | |
| } | |
| .btn-devtools:hover { color: #e6edf3; border-color: #a855f7; } | |
| .btn-devtools.active { color: #a855f7; border-color: #7c3aed; background: #13111e; } | |
| .split-layout { flex: 1; display: flex; gap: 20px; width: 100%; overflow: hidden; } | |
| .chat-column { flex: 1; display: flex; flex-direction: column; overflow: hidden; } | |
| .hidden-split { display: none ; } | |
| /* Cyberpunk Glassmorphism Overrides */ | |
| .sidebar, .glassbox-panel, .dialogue-area, .input-area, .header, .controls, .header-left .logo { | |
| background: rgba(10, 14, 23, 0.65) ; | |
| backdrop-filter: blur(16px) ; | |
| border: 1px solid rgba(0, 243, 255, 0.15) ; | |
| box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5) ; | |
| } | |
| button:hover, .btn:hover, .btn-devtools:hover, .nav-link:hover, .task-card:hover { | |
| color: #00f3ff ; | |
| border-color: #00f3ff ; | |
| box-shadow: inset 0 0 10px rgba(0, 243, 255, 0.3) ; | |
| transition: all 0.3s ease ; | |
| } | |
| .neon-btn { | |
| background: transparent; color: #00f3ff; border: 1px solid #00f3ff; | |
| padding: 6px 14px; border-radius: 8px; font-size: 12px; font-weight: bold; | |
| cursor: pointer; transition: all 0.3s; | |
| box-shadow: 0 0 10px rgba(0,243,255,0.2); | |
| } | |
| .neon-btn:hover { | |
| background: rgba(0,243,255,0.1) ; box-shadow: 0 0 20px rgba(0,243,255,0.5) ; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <div class="header-left"> | |
| <div class="logo">π</div> | |
| <div> | |
| <h1>SocraticEnv</h1> | |
| <p>OpenEnv Hackathon Β· Meta Γ PyTorch Γ Scaler</p> | |
| </div> | |
| </div> | |
| <div class="header-right"> | |
| <a href="/ui/index.html" class="nav-link active">Live Demo</a> | |
| <a href="/ui/leaderboard.html" class="nav-link">π Leaderboard</a> | |
| <a href="/docs" class="nav-link">API Docs</a> | |
| <button onclick="openTeamModal()" class="neon-btn" style="margin: 0 10px;">CodeDriven Initiative</button> | |
| <div class="status-badge"> | |
| <div class="status-dot" id="statusDot"></div> | |
| <span id="statusText">Connecting...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="container"> | |
| <div class="sidebar"> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-title">Choose a Task</div> | |
| <div class="task-card active" onclick="selectTask('factual_recall')" id="card-factual_recall"> | |
| <div class="task-header"> | |
| <span class="task-name">Factual Recall</span> | |
| <span class="difficulty easy">Easy</span> | |
| </div> | |
| <div class="task-desc">Agent explains a concept. Graded on accuracy, key terms, and rejecting misconceptions.</div> | |
| </div> | |
| <div class="task-card" onclick="selectTask('socratic_dialogue')" id="card-socratic_dialogue"> | |
| <div class="task-header"> | |
| <span class="task-name">Socratic Dialogue</span> | |
| <span class="difficulty medium">Medium</span> | |
| </div> | |
| <div class="task-desc">5-turn philosophical dialogue. Graded on reasoning depth and coherence.</div> | |
| </div> | |
| <div class="task-card" onclick="selectTask('misconception_trap')" id="card-misconception_trap"> | |
| <div class="task-header"> | |
| <span class="task-name">Misconception Trap</span> | |
| <span class="difficulty hard">Hard</span> | |
| </div> | |
| <div class="task-desc">Tutor plants a false belief. Agent must detect, correct, and explain.</div> | |
| </div> | |
| <div class="task-card" onclick="selectTask('debate_mode')" id="card-debate_mode"> | |
| <div class="task-header"> | |
| <span class="task-name">Debate Mode</span> | |
| <span class="difficulty medium">Medium</span> | |
| </div> | |
| <div class="task-desc">Agent argues both sides of a topic. Graded on argument quality and use of evidence.</div> | |
| </div> | |
| <div class="task-card" onclick="selectTask('analogy_challenge')" id="card-analogy_challenge"> | |
| <div class="task-header"> | |
| <span class="task-name">Analogy Challenge</span> | |
| <span class="difficulty hard">Hard</span> | |
| </div> | |
| <div class="task-desc">Explain complex concepts using ONLY analogies. No technical jargon allowed!</div> | |
| </div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-title">Generate Custom Task</div> | |
| <div style="margin-bottom:8px;"> | |
| <input | |
| id="topicInput" | |
| placeholder="Any topic... e.g. Black holes" | |
| style="width:100%;background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:8px 10px;color:#e6edf3;font-size:12px;font-family:inherit;outline:none;" | |
| onkeydown="if(event.key==='Enter') generateTask()" | |
| /> | |
| </div> | |
| <div style="display:flex;gap:6px;margin-bottom:8px;"> | |
| <select id="genDifficulty" style="flex:1;background:#21262d;border:1px solid #30363d;color:#e6edf3;border-radius:6px;padding:5px 8px;font-size:11px;"> | |
| <option value="easy">Easy β Factual</option> | |
| <option value="medium" selected>Medium β Socratic</option> | |
| <option value="hard">Hard β Trap</option> | |
| <option value="debate">Medium β Debate</option> | |
| <option value="analogy">Hard β Analogy</option> | |
| </select> | |
| <button | |
| onclick="generateTask()" | |
| id="generateBtn" | |
| style="flex:2;background:#7c3aed;color:white;border:none;border-radius:6px;padding:5px 10px;font-size:11px;font-weight:600;cursor:pointer;"> | |
| β¨ Generate | |
| </button> | |
| </div> | |
| <div id="generateStatus" style="font-size:11px;color:#8b949e;min-height:16px;line-height:1.4;"></div> | |
| </div> | |
| <div class="score-grid"> | |
| <div class="score-card full"> | |
| <div class="score-value" id="overallScore">β</div> | |
| <div class="score-label">Overall Score</div> | |
| </div> | |
| <div class="score-card"> | |
| <div class="score-value" id="turnCount" style="color:#d29922">0</div> | |
| <div class="score-label">Turns</div> | |
| </div> | |
| <div class="score-card"> | |
| <div class="score-value" id="lastReward" style="color:#3fb950">β</div> | |
| <div class="score-label">Last Reward</div> | |
| </div> | |
| </div> | |
| <div class="sidebar-section" id="chartSection" style="display:none;"> | |
| <div class="sidebar-title">Score Progression</div> | |
| <div class="chart-container"> | |
| <canvas id="scoreChart" width="100" height="60"></canvas> | |
| </div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-title">Turn Progress</div> | |
| <div class="turn-track" id="turnTrack"></div> | |
| <div style="font-size:11px;color:#8b949e;margin-top:8px" id="turnLabel">No active episode</div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-title">Session History</div> | |
| <div id="sessionHistory" style="font-size:12px;color:#8b949e;"> | |
| No completed episodes yet. | |
| </div> | |
| </div> | |
| </div> | |
| <div class="main"> | |
| <div class="controls"> | |
| <button class="btn btn-primary" id="btnStart" onclick="startEpisode()">βΆ Start Episode</button> | |
| <button class="btn btn-secondary" id="btnAutoRun" onclick="toggleAutoRun()">β‘ Auto-Run AI</button> | |
| <button class="btn btn-danger" onclick="resetAll()">βΊ Reset</button> | |
| <span id="taxonomy-badge" style="display:none; padding: 4px 8px; border-radius: 4px; font-weight: bold; margin-left: 10px; background: #3b82f6; color: white; font-size: 0.8rem;"></span> | |
| <div class="controls-right"> | |
| <label style="display:flex;align-items:center;gap:6px;color:#9ca3af;font-size:12px;cursor:pointer;"> | |
| <input type="checkbox" id="split-screen-toggle" onchange="toggleSplitScreen()"> Live Comparison | |
| </label> | |
| <button id="btn-export-evals" style="display:none;" onclick="exportOpenAIEvals()" class="btn btn-secondary">Export Evals JSON</button> | |
| <button onclick="viewHeatmap()" class="btn btn-secondary">π Heatmap</button> | |
| <button class="btn-devtools" id="btnDevtools" onclick="toggleGlassBox()"> | |
| <span>π¬</span> Reward Math | |
| </button> | |
| <span class="speed-label">Speed:</span> | |
| <select class="speed-select" id="speedSelect"> | |
| <option value="2000">Slow</option> | |
| <option value="1000" selected>Normal</option> | |
| <option value="400">Fast</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="split-layout"> | |
| <div class="chat-column" id="baseline-chat"> | |
| <div class="dialogue-area" id="dialogueArea"> | |
| <div class="empty-state" id="emptyState"> | |
| <div class="empty-icon">π</div> | |
| <div class="empty-title">SocraticEnv is ready</div> | |
| <div class="empty-sub">Select a task and click Start Episode</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chat-column hidden-split" id="grpo-chat"> | |
| <h3 style="color: #a855f7; padding: 14px 20px 0; font-size: 14px; font-weight: 700;">GRPO Trained Model</h3> | |
| <div class="model-status-overlay"> | |
| <h3 class="gradient-text">GRPO Model v1.0</h3> | |
| <p><strong>Status:</strong> Weights Trained & Verified β </p> | |
| <p><strong>Improvement:</strong> +0.292 Overall Score</p> | |
| <p class="coming-soon-tag">Live Dual-Inference Coming Soon</p> | |
| <div class="progress-bar-mini"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="input-area"> | |
| <div class="autorun-banner" id="autorunBanner"> | |
| <span>β‘</span> | |
| <span id="autorunStatus">Auto-Run mode β AI is thinking...</span> | |
| </div> | |
| <div class="input-row"> | |
| <textarea | |
| class="input-box" id="inputBox" | |
| placeholder="Type your response as the student agent... (or use Auto-Run AI)" | |
| rows="1" disabled onkeydown="handleKey(event)" | |
| ></textarea> | |
| <button class="btn-send" id="btnSend" onclick="sendManual()" disabled>β€</button> | |
| </div> | |
| <div class="input-hint"> | |
| <span>Press Enter to send Β· Shift+Enter for new line</span> | |
| <span id="taskHint">No active task</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Glass Box Inspector Panel --> | |
| <div class="glassbox-panel" id="glassboxPanel"> | |
| <div class="glassbox-header"> | |
| <h3>π¬ Reward Math <span class="badge">V3 DevTools</span></h3> | |
| </div> | |
| <div class="glassbox-body" id="glassboxBody"> | |
| <div class="glassbox-empty" id="glassboxEmpty"> | |
| <div class="icon">βοΈ</div> | |
| <div>Run an episode to inspect<br>the V3 anti-hack reward math.</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API = window.location.origin; | |
| let selectedTask = 'factual_recall'; | |
| let episodeActive = false; | |
| let autoRunning = false; | |
| let autoRunTimer = null; | |
| let totalScore = 0; | |
| let turnCount = 0; | |
| let maxTurns = 3; | |
| let sessionResults = []; | |
| let currentHistory = []; | |
| let sessionId = null; | |
| let generatedTaskId = null; | |
| // NEW: Globals for Chart and Export Data | |
| let scoreChartInstance = null; | |
| let exportData = null; | |
| async function checkStatus() { | |
| try { | |
| const r = await fetch(`${API}/ping`); | |
| const dot = document.getElementById('statusDot'); | |
| const txt = document.getElementById('statusText'); | |
| if (r.ok) { | |
| dot.classList.remove('offline'); | |
| txt.textContent = 'Environment online'; | |
| } else { | |
| dot.classList.add('offline'); | |
| txt.textContent = 'Environment offline'; | |
| } | |
| } catch { | |
| document.getElementById('statusDot').classList.add('offline'); | |
| document.getElementById('statusText').textContent = 'Cannot connect'; | |
| } | |
| } | |
| checkStatus(); | |
| setInterval(checkStatus, 5000); | |
| function selectTask(taskId) { | |
| selectedTask = taskId; | |
| document.querySelectorAll('.task-card').forEach(c => c.classList.remove('active')); | |
| document.getElementById(`card-${taskId}`).classList.add('active'); | |
| const hints = { | |
| factual_recall: 'Easy β Explain a concept clearly', | |
| socratic_dialogue: 'Medium β Engage in 5-turn reasoning', | |
| misconception_trap:'Hard β Catch the planted false belief!', | |
| debate_mode: 'Medium β Argue both sides convincingly', | |
| analogy_challenge: 'Hard β No jargon, analogies only!', | |
| }; | |
| document.getElementById('taskHint').textContent = hints[taskId]; | |
| } | |
| // NEW: Initialize the Chart.js graph | |
| function initChart() { | |
| const ctx = document.getElementById('scoreChart').getContext('2d'); | |
| if (scoreChartInstance) { | |
| scoreChartInstance.destroy(); | |
| } | |
| scoreChartInstance = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: [], | |
| datasets: [{ | |
| label: 'Reward Score', | |
| data: [], | |
| borderColor: '#a855f7', | |
| backgroundColor: '#a855f720', | |
| borderWidth: 2, | |
| tension: 0.3, | |
| fill: true, | |
| pointBackgroundColor: '#e6edf3' | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| min: 0, | |
| max: 1.05, | |
| grid: { color: '#30363d' }, | |
| ticks: { color: '#8b949e', font: { size: 10 } } | |
| }, | |
| x: { | |
| grid: { display: false }, | |
| ticks: { color: '#8b949e', font: { size: 10 } } | |
| } | |
| }, | |
| plugins: { | |
| legend: { display: false } | |
| } | |
| } | |
| }); | |
| document.getElementById('chartSection').style.display = 'block'; | |
| } | |
| // NEW: Update Chart data | |
| function updateChart(turn, score) { | |
| if (!scoreChartInstance) return; | |
| scoreChartInstance.data.labels.push(`Turn ${turn}`); | |
| scoreChartInstance.data.datasets[0].data.push(score); | |
| scoreChartInstance.update(); | |
| } | |
| async function startEpisode() { | |
| clearDialogue(); | |
| episodeActive = true; | |
| totalScore = 0; | |
| turnCount = 0; | |
| currentHistory = []; | |
| // NEW: Reset export structure and chart | |
| exportData = { | |
| task: selectedTask, | |
| timestamp: new Date().toLocaleString(), | |
| turns: [], | |
| final_score: 0, | |
| feedback: '' | |
| }; | |
| initChart(); | |
| const maxMap = { factual_recall: 3, socratic_dialogue: 5, misconception_trap: 3, debate_mode: 4, analogy_challenge: 3 }; | |
| maxTurns = maxMap[selectedTask]; | |
| buildTurnTrack(maxTurns); | |
| updateScores(); | |
| document.getElementById('btnStart').disabled = true; | |
| document.getElementById('emptyState')?.remove(); | |
| try { | |
| const resetBody = { task_id: selectedTask }; | |
| if (generatedTaskId) { | |
| resetBody.generated_task_id = generatedTaskId; | |
| generatedTaskId = null; | |
| } | |
| const r = await fetch(`${API}/reset`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(resetBody), | |
| }); | |
| const data = await r.json(); | |
| sessionId = data.session_id; | |
| const question = data.observation.question; | |
| currentHistory.push({ role: 'tutor', content: question }); | |
| // NEW: Record initial state for export | |
| exportData.turns.push({ turn: 0, tutor_opening: question }); | |
| addTutorMessage(question); | |
| enableInput(); | |
| document.getElementById('turnLabel').textContent = `Turn 1 of ${maxTurns}`; | |
| } catch (e) { | |
| addSystemMessage('β Could not connect to environment.', 'error'); | |
| document.getElementById('btnStart').disabled = false; | |
| episodeActive = false; | |
| } | |
| } | |
| async function sendResponse(response) { | |
| if (!episodeActive || !response || !response.trim()) return; | |
| disableInput(); | |
| addAgentMessage(response); | |
| currentHistory.push({ role: 'agent', content: response }); | |
| showTyping(); | |
| await sleep(300); | |
| try { | |
| const r = await fetch(`${API}/step`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ response, session_id: sessionId }), | |
| }); | |
| const data = await r.json(); | |
| removeTyping(); | |
| turnCount++; | |
| const score = data.reward.score; | |
| totalScore += score; | |
| updateScores(score); | |
| updateTurnTrack(turnCount); | |
| // NEW: Update chart visually | |
| updateChart(turnCount, score); | |
| const nextQuestion = data.observation.question; | |
| currentHistory.push({ role: 'tutor', content: nextQuestion }); | |
| // NEW: Save turn data for export | |
| exportData.turns.push({ | |
| turn: turnCount, | |
| agent_response: response, | |
| reward_score: score, | |
| breakdown: data.reward.breakdown, | |
| tutor_next: nextQuestion | |
| }); | |
| // Glass Box: render the breakdown | |
| renderGlassBox(turnCount, score, data.reward.breakdown); | |
| addTutorMessage(nextQuestion, data.reward); | |
| if (data.done) { | |
| episodeActive = false; | |
| stopAutoRun(); | |
| const avg = totalScore / turnCount; | |
| // NEW: Finalize export data | |
| exportData.final_score = avg; | |
| exportData.feedback = data.reward.feedback; | |
| showComplete(avg, data.reward.feedback); | |
| saveToHistory(selectedTask, avg); | |
| document.getElementById('btnStart').disabled = false; | |
| } else { | |
| if (!autoRunning) enableInput(); | |
| } | |
| } catch (e) { | |
| removeTyping(); | |
| addSystemMessage(`β Step error: ${e.message}`, 'error'); | |
| enableInput(); | |
| } | |
| } | |
| function sendManual() { | |
| const box = document.getElementById('inputBox'); | |
| const val = box.value.trim(); | |
| if (!val) return; | |
| box.value = ''; | |
| box.style.height = '44px'; | |
| sendResponse(val); | |
| } | |
| function handleKey(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendManual(); } | |
| } | |
| async function getAIResponse(question, history) { | |
| document.getElementById('autorunStatus').textContent = 'β‘ AI is thinking...'; | |
| try { | |
| const r = await fetch(`${API}/inference`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ message: question, history: history }), | |
| }); | |
| if (!r.ok) { | |
| const err = await r.text(); | |
| addSystemMessage(`β οΈ Inference API error ${r.status}: ${err}`, 'warning'); | |
| return null; | |
| } | |
| const data = await r.json(); | |
| if (data.response && data.response.startsWith('ERROR:')) { | |
| addSystemMessage(`β οΈ ${data.response}`, 'warning'); | |
| return null; | |
| } | |
| document.getElementById('autorunStatus').textContent = 'β‘ Auto-Run mode β AI is responding'; | |
| return data.response; | |
| } catch (e) { | |
| addSystemMessage(`β οΈ Could not reach /inference: ${e.message}`, 'warning'); | |
| return null; | |
| } | |
| } | |
| function toggleAutoRun() { | |
| if (autoRunning) { stopAutoRun(); return; } | |
| if (!episodeActive) { | |
| startEpisode().then(() => { | |
| if (episodeActive) { autoRunning = true; startAutoRun(); } | |
| }); | |
| } else { | |
| autoRunning = true; | |
| startAutoRun(); | |
| } | |
| } | |
| function startAutoRun() { | |
| autoRunning = true; | |
| document.getElementById('autorunBanner').classList.add('visible'); | |
| document.getElementById('btnAutoRun').textContent = 'βΉ Stop Auto-Run'; | |
| disableInput(); | |
| runNextAutoStep(); | |
| } | |
| async function runNextAutoStep() { | |
| if (!autoRunning || !episodeActive) return; | |
| const speed = parseInt(document.getElementById('speedSelect').value); | |
| await sleep(speed); | |
| if (!autoRunning || !episodeActive) return; | |
| const tutorMessages = currentHistory.filter(h => h.role === 'tutor'); | |
| if (tutorMessages.length === 0) { stopAutoRun(); return; } | |
| const lastQuestion = tutorMessages[tutorMessages.length - 1].content; | |
| const response = await getAIResponse(lastQuestion, currentHistory); | |
| if (!response) { | |
| stopAutoRun(); | |
| addSystemMessage('β οΈ Auto-Run stopped. Check HuggingFace Space secrets or type manually.', 'warning'); | |
| if (episodeActive) enableInput(); | |
| return; | |
| } | |
| if (!autoRunning || !episodeActive) return; | |
| await sendResponse(response); | |
| if (episodeActive && autoRunning) runNextAutoStep(); | |
| } | |
| function stopAutoRun() { | |
| autoRunning = false; | |
| clearTimeout(autoRunTimer); | |
| document.getElementById('autorunBanner').classList.remove('visible'); | |
| document.getElementById('btnAutoRun').textContent = 'β‘ Auto-Run AI'; | |
| if (episodeActive) enableInput(); | |
| } | |
| function resetAll() { | |
| episodeActive = false; | |
| autoRunning = false; | |
| currentHistory = []; | |
| exportData = null; | |
| sessionId = null; | |
| generatedTaskId = null; | |
| clearTimeout(autoRunTimer); | |
| stopAutoRun(); | |
| clearDialogue(); | |
| totalScore = 0; turnCount = 0; | |
| document.getElementById('overallScore').textContent = 'β'; | |
| document.getElementById('turnCount').textContent = '0'; | |
| document.getElementById('lastReward').textContent = 'β'; | |
| document.getElementById('turnTrack').innerHTML = ''; | |
| document.getElementById('turnLabel').textContent = 'No active episode'; | |
| document.getElementById('btnStart').disabled = false; | |
| document.getElementById('chartSection').style.display = 'none'; | |
| clearGlassBox(); | |
| if(scoreChartInstance) scoreChartInstance.destroy(); | |
| disableInput(); | |
| document.getElementById('dialogueArea').innerHTML = | |
| `<div class="empty-state" id="emptyState"> | |
| <div class="empty-icon">π</div> | |
| <div class="empty-title">SocraticEnv is ready</div> | |
| <div class="empty-sub">Select a task and click Start Episode</div> | |
| </div>`; | |
| } | |
| function addTutorMessage(text, reward = null) { | |
| const area = document.getElementById('dialogueArea'); | |
| const div = document.createElement('div'); | |
| div.className = 'message tutor'; | |
| let rewardHtml = '', breakdownHtml = ''; | |
| if (reward) { | |
| const sc = reward.score; | |
| const cls = sc >= 0.7 ? 'reward-high' : sc >= 0.4 ? 'reward-mid' : 'reward-low'; | |
| rewardHtml = `<span class="reward-pill ${cls}">+${sc.toFixed(3)}</span>`; | |
| const bd = Object.entries(reward.breakdown) | |
| .map(([k,v]) => `<span class="breakdown-item">${k}: ${v}</span>`).join(''); | |
| breakdownHtml = `<div class="breakdown">${bd}</div>`; | |
| } | |
| div.innerHTML = ` | |
| <div class="avatar">π</div> | |
| <div> | |
| <div class="bubble">${text}</div> | |
| <div class="bubble-meta">Tutor ${rewardHtml}</div> | |
| ${breakdownHtml} | |
| </div>`; | |
| area.appendChild(div); | |
| area.scrollTop = area.scrollHeight; | |
| } | |
| function addAgentMessage(text) { | |
| const area = document.getElementById('dialogueArea'); | |
| const div = document.createElement('div'); | |
| div.className = 'message agent'; | |
| div.innerHTML = ` | |
| <div class="avatar">π€</div> | |
| <div> | |
| <div class="bubble">${text}</div> | |
| <div class="bubble-meta">Agent</div> | |
| </div>`; | |
| area.appendChild(div); | |
| area.scrollTop = area.scrollHeight; | |
| } | |
| function addSystemMessage(text, type = '') { | |
| const area = document.getElementById('dialogueArea'); | |
| const div = document.createElement('div'); | |
| div.className = `system-msg ${type}`; | |
| div.textContent = text; | |
| area.appendChild(div); | |
| area.scrollTop = area.scrollHeight; | |
| } | |
| function showTyping() { | |
| const area = document.getElementById('dialogueArea'); | |
| const div = document.createElement('div'); | |
| div.className = 'typing'; div.id = 'typingIndicator'; | |
| div.innerHTML = ` | |
| <div class="avatar">π€</div> | |
| <div class="typing-dots"> | |
| <div class="dot"></div><div class="dot"></div><div class="dot"></div> | |
| </div>`; | |
| area.appendChild(div); | |
| area.scrollTop = area.scrollHeight; | |
| } | |
| function removeTyping() { document.getElementById('typingIndicator')?.remove(); } | |
| function showComplete(score, feedback) { | |
| const area = document.getElementById('dialogueArea'); | |
| const div = document.createElement('div'); | |
| // NEW: Added the Export buttons to the completion banner | |
| div.innerHTML = ` | |
| <div style="display:flex; flex-direction:column; gap:12px;"> | |
| <div class="complete-banner"> | |
| <div class="complete-left"> | |
| <div class="complete-icon">${score >= 0.7 ? 'π' : score >= 0.5 ? 'β ' : 'π'}</div> | |
| <div> | |
| <div class="complete-title">Episode Complete</div> | |
| <div class="complete-sub">${feedback}</div> | |
| </div> | |
| </div> | |
| <div class="final-score">${score.toFixed(3)}</div> | |
| </div> | |
| <div style="display:flex; gap:10px;"> | |
| <button class="btn btn-secondary" style="flex:1; justify-content:center" onclick="downloadExport('json')"> | |
| π Download JSON Data | |
| </button> | |
| <button class="btn btn-secondary" style="flex:1; justify-content:center" onclick="downloadExport('txt')"> | |
| π Download Readable Report | |
| </button> | |
| </div> | |
| </div>`; | |
| area.appendChild(div); | |
| area.scrollTop = area.scrollHeight; | |
| document.getElementById('overallScore').textContent = score.toFixed(3); | |
| document.getElementById('overallScore').style.color = | |
| score >= 0.7 ? '#3fb950' : score >= 0.5 ? '#d29922' : '#f85149'; | |
| } | |
| // NEW: Functions to handle downloading the Episode Report | |
| function downloadExport(format) { | |
| if (!exportData) return; | |
| let content = ""; | |
| let mimeType = ""; | |
| let filename = `SocraticEnv_${exportData.task}_${Date.now()}`; | |
| if (format === 'json') { | |
| content = JSON.stringify(exportData, null, 2); | |
| mimeType = "application/json"; | |
| filename += ".json"; | |
| } else if (format === 'txt') { | |
| // Generate a beautifully formatted Markdown/Text report | |
| content = `================================================ | |
| SOCRATIC ENV β EVALUATION REPORT | |
| ================================================ | |
| Task: ${exportData.task} | |
| Timestamp: ${exportData.timestamp} | |
| Final Score: ${exportData.final_score.toFixed(3)} | |
| Feedback: ${exportData.feedback} | |
| ------------------------------------------------\n\n`; | |
| exportData.turns.forEach(t => { | |
| if (t.turn === 0) { | |
| content += `[TUTOR OPENING]\n${t.tutor_opening}\n\n`; | |
| } else { | |
| content += `[TURN ${t.turn}]\n`; | |
| content += `Agent: ${t.agent_response}\n`; | |
| content += `Reward: ${t.reward_score.toFixed(3)}\n`; | |
| content += `Breakdown: ${JSON.stringify(t.breakdown)}\n`; | |
| content += `Tutor: ${t.tutor_next}\n\n`; | |
| } | |
| }); | |
| mimeType = "text/plain"; | |
| filename += ".txt"; | |
| } | |
| const blob = new Blob([content], { type: mimeType }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| function clearDialogue() { document.getElementById('dialogueArea').innerHTML = ''; } | |
| function enableInput() { | |
| document.getElementById('inputBox').disabled = false; | |
| document.getElementById('btnSend').disabled = false; | |
| document.getElementById('inputBox').focus(); | |
| } | |
| function disableInput() { | |
| document.getElementById('inputBox').disabled = true; | |
| document.getElementById('btnSend').disabled = true; | |
| } | |
| function buildTurnTrack(n) { | |
| const track = document.getElementById('turnTrack'); | |
| track.innerHTML = ''; | |
| for (let i = 0; i < n; i++) { | |
| const d = document.createElement('div'); | |
| d.className = 'turn-dot'; d.id = `dot-${i}`; | |
| track.appendChild(d); | |
| } | |
| } | |
| function updateTurnTrack(turn) { | |
| for (let i = 0; i < maxTurns; i++) { | |
| const d = document.getElementById(`dot-${i}`); | |
| if (!d) continue; | |
| if (i < turn) d.className = 'turn-dot done'; | |
| else if (i===turn) d.className = 'turn-dot current'; | |
| else d.className = 'turn-dot'; | |
| } | |
| document.getElementById('turnLabel').textContent = `Turn ${turn} of ${maxTurns}`; | |
| } | |
| function updateScores(lastReward = null) { | |
| document.getElementById('turnCount').textContent = turnCount; | |
| if (lastReward !== null) { | |
| document.getElementById('lastReward').textContent = lastReward.toFixed(3); | |
| document.getElementById('lastReward').style.color = | |
| lastReward >= 0.7 ? '#3fb950' : lastReward >= 0.4 ? '#d29922' : '#f85149'; | |
| } | |
| if (turnCount > 0) { | |
| document.getElementById('overallScore').textContent = | |
| (totalScore / turnCount).toFixed(3); | |
| } | |
| } | |
| function saveToHistory(task, score) { | |
| sessionResults.unshift({ task, score }); | |
| document.getElementById('sessionHistory').innerHTML = | |
| sessionResults.slice(0, 5).map(r => ` | |
| <div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #21262d;"> | |
| <span style="color:#c9d1d9">${r.task.replace(/_/g,' ')}</span> | |
| <span style="color:${r.score>=0.7?'#3fb950':r.score>=0.5?'#d29922':'#f85149'};font-weight:600"> | |
| ${r.score.toFixed(3)} | |
| </span> | |
| </div>`).join(''); | |
| } | |
| async function generateTask() { | |
| const topic = document.getElementById('topicInput').value.trim(); | |
| const difficulty = document.getElementById('genDifficulty').value; | |
| const status = document.getElementById('generateStatus'); | |
| const btn = document.getElementById('generateBtn'); | |
| if (!topic) { | |
| status.textContent = 'β οΈ Please enter a topic first.'; | |
| status.style.color = '#d29922'; | |
| return; | |
| } | |
| btn.disabled = true; | |
| btn.textContent = 'β³ Generating...'; | |
| status.style.color = '#a855f7'; | |
| status.textContent = `Generating ${difficulty} task about "${topic}"...`; | |
| try { | |
| const r = await fetch(`${API}/generate_task`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ topic, difficulty, task_type: difficulty }), | |
| }); | |
| const data = await r.json(); | |
| if (data.error) { | |
| status.style.color = '#f85149'; | |
| status.textContent = `β ${data.error}`; | |
| } else { | |
| status.style.color = '#3fb950'; | |
| status.textContent = `β Ready! "${data.preview.substring(0, 60)}..."`; | |
| generatedTaskId = data.generated_task_id || null; | |
| selectTask(data.task_id); | |
| document.getElementById('topicInput').value = ''; | |
| updateTaxonomyBadge(data.taxonomy_class || null); | |
| } | |
| } catch(e) { | |
| status.style.color = '#f85149'; | |
| status.textContent = `β ${e.message}`; | |
| } finally { | |
| btn.disabled = false; | |
| btn.textContent = 'β¨ Generate'; | |
| } | |
| } | |
| // ββ Glass Box Inspector ββββββββββββββββββββββββββββββββββ | |
| function toggleGlassBox() { | |
| const container = document.querySelector('.container'); | |
| const btn = document.getElementById('btnDevtools'); | |
| container.classList.toggle('devtools-open'); | |
| btn.classList.toggle('active'); | |
| } | |
| function renderGlassBox(turn, score, breakdown) { | |
| const body = document.getElementById('glassboxBody'); | |
| const empty = document.getElementById('glassboxEmpty'); | |
| if (empty) empty.remove(); | |
| const penaltyKeys = new Set([ | |
| 'penalty_too_short', 'rambling_penalty', 'keyword_spam_penalty', | |
| 'parroting_penalty', 'syntax_penalty', 'jargon_penalty', | |
| 'trap_missed_penalty' | |
| ]); | |
| // Determine score tier | |
| let tierClass = 'low'; | |
| if (score >= 0.7) tierClass = 'high'; | |
| else if (score >= 0.4) tierClass = 'mid'; | |
| const card = document.createElement('div'); | |
| card.className = 'gb-turn-card'; | |
| let rowsHtml = ''; | |
| const sorted = Object.entries(breakdown).sort((a, b) => { | |
| // Penalties first (negative), then positives | |
| const aNeg = a[1] < 0 ? 0 : 1; | |
| const bNeg = b[1] < 0 ? 0 : 1; | |
| if (aNeg !== bNeg) return aNeg - bNeg; | |
| return Math.abs(b[1]) - Math.abs(a[1]); | |
| }); | |
| for (const [key, val] of sorted) { | |
| const isNeg = val < 0 || penaltyKeys.has(key); | |
| const cls = isNeg ? 'negative' : (val > 0 ? 'positive' : 'neutral'); | |
| const sign = val > 0 ? '+' : ''; | |
| const displayVal = typeof val === 'number' ? `${sign}${val.toFixed(3)}` : val; | |
| const displayKey = key.replace(/_/g, ' '); | |
| rowsHtml += `<div class="gb-row ${cls}"><span class="gb-key">${displayKey}</span><span class="gb-val">${displayVal}</span></div>`; | |
| } | |
| card.innerHTML = ` | |
| <div class="gb-turn-header"> | |
| <span class="gb-turn-label">Turn ${turn}</span> | |
| <span class="gb-turn-score ${tierClass}">${score.toFixed(3)}</span> | |
| </div> | |
| <div class="gb-breakdown-list">${rowsHtml}</div> | |
| `; | |
| body.appendChild(card); | |
| card.scrollIntoView({ behavior: 'smooth', block: 'end' }); | |
| } | |
| function clearGlassBox() { | |
| const body = document.getElementById('glassboxBody'); | |
| body.innerHTML = `<div class="glassbox-empty" id="glassboxEmpty"><div class="icon">βοΈ</div><div>Run an episode to inspect<br>the V3 anti-hack reward math.</div></div>`; | |
| } | |
| // ββ Research UI Functions ββββββββββββββββββββββββββββββββ | |
| function toggleSplitScreen() { | |
| const isChecked = document.getElementById('split-screen-toggle').checked; | |
| const grpoCol = document.getElementById('grpo-chat'); | |
| if (isChecked) grpoCol.classList.remove('hidden-split'); | |
| else grpoCol.classList.add('hidden-split'); | |
| } | |
| async function exportOpenAIEvals() { | |
| if (!sessionId) return alert('No active session to export.'); | |
| try { | |
| const res = await fetch(`${API}/export_evals/${sessionId}`); | |
| const data = await res.json(); | |
| const structuredReport = { | |
| session_id: data.session_id, | |
| task_id: data.task_id, | |
| final_score: data.final_score, | |
| total_samples: data.total_samples, | |
| evals: data.lines | |
| }; | |
| const blob = new Blob([JSON.stringify(structuredReport, null, 2)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `socratic_evals_${sessionId.substring(0,6)}.json`; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } catch (err) { alert('Failed to export. Ensure episode is done.'); } | |
| } | |
| function updateTaxonomyBadge(taxonomyClass) { | |
| const badge = document.getElementById('taxonomy-badge'); | |
| if (taxonomyClass) { | |
| badge.style.display = 'inline-block'; | |
| badge.innerText = taxonomyClass.replace('_', ' ').toUpperCase(); | |
| badge.style.background = taxonomyClass === 'scientific_misconception' ? '#ef4444' : | |
| taxonomyClass === 'false_authority' ? '#f59e0b' : '#3b82f6'; | |
| } else badge.style.display = 'none'; | |
| } | |
| async function viewHeatmap() { | |
| try { | |
| const res = await fetch(`${API}/heatmap`); | |
| const data = await res.json(); | |
| console.log(`--- Curriculum Heatmap (Total Episodes: ${data.total_episodes}) ---`); | |
| console.table(data.taxonomy_classes); | |
| alert(`Heatmap data fetched! ${data.total_episodes} total episodes. Check browser console for visual grid.`); | |
| } catch (err) { alert('Heatmap endpoint not available yet.'); } | |
| } | |
| function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } | |
| document.getElementById('inputBox').addEventListener('input', function() { | |
| this.style.height = '44px'; | |
| this.style.height = Math.min(this.scrollHeight, 120) + 'px'; | |
| }); | |
| // ββ CodeDriven Modal & Animations βββββββββββββββββββββββ | |
| function openTeamModal() { | |
| document.getElementById('teamModal').style.display = 'flex'; | |
| } | |
| function closeTeamModal() { | |
| document.getElementById('teamModal').style.display = 'none'; | |
| document.getElementById('card-amar').style.height = '250px'; | |
| document.getElementById('card-saranya').style.height = '250px'; | |
| document.getElementById('desc-amar').style.display = 'none'; | |
| document.getElementById('desc-saranya').style.display = 'none'; | |
| document.getElementById('desc-amar').innerHTML = ''; | |
| document.getElementById('desc-saranya').innerHTML = ''; | |
| } | |
| function expandProfile(cardId, descId, text) { | |
| const card = document.getElementById(cardId); | |
| const desc = document.getElementById(descId); | |
| if (card.style.height === '400px') return; // already expanded | |
| card.style.height = '400px'; | |
| setTimeout(() => { | |
| desc.style.display = 'block'; | |
| typeWriterEffect(descId, text); | |
| }, 300); | |
| } | |
| function typeWriterEffect(elementId, text) { | |
| const el = document.getElementById(elementId); | |
| el.innerHTML = ''; | |
| let i = 0; | |
| function type() { | |
| if (i < text.length) { | |
| el.innerHTML += text.charAt(i); | |
| i++; | |
| setTimeout(type, 15); | |
| } | |
| } | |
| type(); | |
| } | |
| </script> | |
| <!-- CodeDriven Team Modal --> | |
| <div id="teamModal" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(5, 11, 20, 0.85); backdrop-filter: blur(20px); z-index: 9999; align-items: center; justify-content: center; flex-direction: column;"> | |
| <div style="position: absolute; top: 20px; right: 30px; font-size: 40px; color: #00f3ff; cursor: pointer; text-shadow: 0 0 10px #00f3ff;" onclick="closeTeamModal()">Γ</div> | |
| <div style="text-align: center; margin-bottom: 40px;"> | |
| <img src="./CodeDriven.jpg" alt="CodeDriven Logo" style="height: 60px; margin-bottom: 15px; border-radius: 8px; box-shadow: 0 0 15px rgba(0,243,255,0.3);"> | |
| <h2 style="color: #00f3ff; letter-spacing: 4px; font-size: 2rem; margin-bottom: 10px; text-transform: uppercase; text-shadow: 0 0 10px rgba(0,243,255,0.5);">CodeDriven Initiative</h2> | |
| </div> | |
| <div style="display: flex; gap: 40px;"> | |
| <!-- Amar Profile --> | |
| <div style="background: rgba(10, 14, 23, 0.7); border: 1px solid rgba(0, 243, 255, 0.3); border-radius: 12px; padding: 20px; width: 300px; text-align: center; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; height: 250px; box-shadow: 0 4px 30px rgba(0,0,0,0.5);" id="card-amar"> | |
| <div style="width: 100px; height: 100px; border-radius: 50%; margin: 0 auto 15px; border: 2px solid #00f3ff; cursor: pointer; background: #1a2332; overflow: hidden; display: flex; align-items: center; justify-content: center; font-size: 40px; box-shadow: inset 0 0 15px rgba(0,243,255,0.2), 0 0 15px rgba(0,243,255,0.2); transition: transform 0.3s;" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'" onclick="expandProfile('card-amar', 'desc-amar', 'Amar Prakash is the visionary Project Lead behind the CodeDriven Initiative. Specializing in adversarial reinforcement learning and agentic AI architectures, Amar architects the core training loops that make SocraticEnv a world-class environment.')"> | |
| <img src="./amar.jpg" alt="Amar" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;"> | |
| </div> | |
| <h3 style="color: #fff; margin-bottom: 5px; font-weight: 600; letter-spacing: 1px;">Amar Prakash</h3> | |
| <p style="color: #00f3ff; font-size: 0.9rem; letter-spacing: 2px; text-transform: uppercase;">Project Lead</p> | |
| <div id="desc-amar" style="margin-top: 20px; text-align: left; font-size: 0.85rem; color: #a8b2d1; display: none; line-height: 1.6; font-family: monospace;"></div> | |
| </div> | |
| <!-- Saranya Profile --> | |
| <div style="background: rgba(10, 14, 23, 0.7); border: 1px solid rgba(0, 243, 255, 0.3); border-radius: 12px; padding: 20px; width: 300px; text-align: center; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; height: 250px; box-shadow: 0 4px 30px rgba(0,0,0,0.5);" id="card-saranya"> | |
| <div style="width: 100px; height: 100px; border-radius: 50%; margin: 0 auto 15px; border: 2px solid #00f3ff; cursor: pointer; background: #1a2332; overflow: hidden; display: flex; align-items: center; justify-content: center; font-size: 40px; box-shadow: inset 0 0 15px rgba(0,243,255,0.2), 0 0 15px rgba(0,243,255,0.2); transition: transform 0.3s;" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'" onclick="expandProfile('card-saranya', 'desc-saranya', 'Saranya is the lead Software Engineer shaping the frontend and backend microservices of SocraticEnv. Her expertise in reactive UIs and robust Python architectures ensures the platform remains highly performant during massive parallel GRPO runs.')"> | |
| <img src="./saranya.jpg" alt="Saranya" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;"> | |
| </div> | |
| <h3 style="color: #fff; margin-bottom: 5px; font-weight: 600; letter-spacing: 1px;">Saranya</h3> | |
| <p style="color: #00f3ff; font-size: 0.9rem; letter-spacing: 2px; text-transform: uppercase;">Software Engineer</p> | |
| <div id="desc-saranya" style="margin-top: 20px; text-align: left; font-size: 0.85rem; color: #a8b2d1; display: none; line-height: 1.6; font-family: monospace;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </body> | |
| </html> |