Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Intern Pipeline</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg: #f5f5f7; | |
| --white: #ffffff; | |
| --border: #e5e5e7; | |
| --border2: #d1d1d6; | |
| --text: #1c1c1e; | |
| --text2: #6c6c70; | |
| --text3: #aeaeb2; | |
| --accent: #5b4ef8; | |
| --accent-light: #ede9fe; | |
| --green: #34c759; | |
| --green-light: #e8f8ec; | |
| --amber: #ff9f0a; | |
| --amber-light: #fff4e0; | |
| --red: #ff3b30; | |
| --red-light: #ffe5e3; | |
| --blue: #007aff; | |
| --radius: 10px; | |
| --radius-lg: 14px; | |
| --shadow: 0 1px 3px rgba(0,0,0,.08); | |
| --shadow-md: 0 4px 12px rgba(0,0,0,.1); | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; } | |
| /* LOGIN */ | |
| #login-screen { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; z-index: 100; } | |
| .login-card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 40px 36px; width: 360px; box-shadow: var(--shadow-md); } | |
| .login-card h1 { font-size: 22px; font-weight: 700; margin-bottom: 6px; letter-spacing: -.3px; } | |
| .login-card p { font-size: 13px; color: var(--text2); margin-bottom: 28px; } | |
| .login-card input { width: 100%; padding: 11px 14px; font-size: 14px; border: 1px solid var(--border2); border-radius: var(--radius); outline: none; font-family: 'JetBrains Mono', monospace; margin-bottom: 12px; transition: border-color .15s; color: var(--text); } | |
| .login-card input:focus { border-color: var(--accent); } | |
| .login-error { font-size: 12px; color: var(--red); margin-top: 8px; display: none; text-align: center; } | |
| /* APP */ | |
| #app { display: none; } | |
| .layout { display: flex; min-height: 100vh; } | |
| /* SIDEBAR */ | |
| .sidebar { width: 240px; min-width: 240px; background: var(--white); border-right: 1px solid var(--border); display: flex; flex-direction: column; padding: 20px 0; position: sticky; top: 0; height: 100vh; } | |
| .sidebar-brand { padding: 4px 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 12px; } | |
| .sidebar-brand h2 { font-size: 17px; font-weight: 700; letter-spacing: -.3px; } | |
| .sidebar-brand p { font-size: 12px; color: var(--text2); margin-top: 2px; font-family: 'JetBrains Mono', monospace; } | |
| .nav-label { font-size: 11px; font-weight: 600; color: var(--text3); letter-spacing: .06em; text-transform: uppercase; padding: 0 20px; margin: 12px 0 4px; } | |
| .nav-item { display: flex; align-items: center; gap: 10px; padding: 9px 20px; font-size: 14px; font-weight: 500; color: var(--text2); cursor: pointer; border: none; background: none; width: 100%; text-align: left; transition: color .12s, background .12s; font-family: 'Inter', sans-serif; } | |
| .nav-item:hover { color: var(--text); background: var(--bg); } | |
| .nav-item.active { color: var(--accent); background: var(--accent-light); font-weight: 600; } | |
| .sidebar-footer { margin-top: auto; padding: 16px 20px; border-top: 1px solid var(--border); font-size: 11px; color: var(--text3); font-family: 'JetBrains Mono', monospace; line-height: 1.6; word-break: break-all; } | |
| /* MAIN */ | |
| .main { flex: 1; overflow-y: auto; } | |
| .topbar { background: var(--white); border-bottom: 1px solid var(--border); padding: 14px 28px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 10; } | |
| .topbar h1 { font-size: 17px; font-weight: 700; letter-spacing: -.3px; } | |
| .topbar-right { display: flex; gap: 8px; align-items: center; } | |
| /* BUTTONS */ | |
| .btn { padding: 8px 16px; font-size: 13px; font-weight: 600; border-radius: var(--radius); border: 1px solid var(--border2); background: var(--white); color: var(--text2); cursor: pointer; font-family: 'Inter', sans-serif; transition: all .12s; } | |
| .btn:hover { color: var(--text); background: var(--bg); } | |
| .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; } | |
| .btn-primary:hover { background: #4a3ed4; border-color: #4a3ed4; color: #fff; } | |
| .btn-sm { padding: 5px 10px; font-size: 12px; } | |
| .content { padding: 24px 28px; } | |
| /* STATS */ | |
| .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; } | |
| .stat-card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px 18px; box-shadow: var(--shadow); } | |
| .stat-card .label { font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 8px; } | |
| .stat-card .value { font-size: 30px; font-weight: 700; letter-spacing: -.5px; } | |
| .stat-card .sub { font-size: 11px; color: var(--text3); margin-top: 3px; font-family: 'JetBrains Mono', monospace; } | |
| /* FILTERS */ | |
| .filters { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; flex-wrap: wrap; } | |
| .filters input, .filters select { padding: 8px 12px; font-size: 13px; border: 1px solid var(--border2); border-radius: var(--radius); outline: none; background: var(--white); color: var(--text); font-family: 'Inter', sans-serif; transition: border-color .12s; } | |
| .filters input { width: 200px; } | |
| .filters input:focus, .filters select:focus { border-color: var(--accent); } | |
| /* TABLE */ | |
| .table-card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow); } | |
| table { width: 100%; border-collapse: collapse; font-size: 13px; } | |
| thead th { background: var(--bg); padding: 10px 16px; text-align: left; font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; border-bottom: 1px solid var(--border); } | |
| tbody tr { border-bottom: 1px solid var(--border); transition: background .1s; } | |
| tbody tr:last-child { border-bottom: none; } | |
| tbody tr:hover { background: var(--bg); } | |
| tbody td { padding: 12px 16px; color: var(--text2); vertical-align: middle; } | |
| tbody td.name { color: var(--text); font-weight: 600; font-size: 14px; } | |
| /* PILLS */ | |
| .pill { display: inline-flex; padding: 3px 9px; border-radius: 20px; font-size: 11px; font-weight: 600; font-family: 'JetBrains Mono', monospace; } | |
| .pill-week0 { background: var(--accent-light); color: var(--accent); } | |
| .pill-intern { background: var(--green-light); color: #1a7a33; } | |
| .pill-probation_w1, .pill-probation_w2 { background: var(--amber-light); color: #7a4d00; } | |
| .status-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; } | |
| .status-badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--green); flex-shrink: 0; } | |
| .status-badge.warned_1::before, .status-badge.warned_2::before { background: var(--amber); } | |
| .status-badge.eliminated::before { background: var(--red); } | |
| .miss-count { font-family: 'JetBrains Mono', monospace; font-size: 13px; } | |
| .miss-0 { color: var(--text3); } .miss-1 { color: var(--amber); font-weight: 600; } .miss-2 { color: var(--red); font-weight: 600; } | |
| .actions { display: flex; gap: 6px; } | |
| .empty-state { text-align: center; padding: 60px 20px; color: var(--text3); font-size: 13px; } | |
| .empty-state .icon { font-size: 32px; margin-bottom: 12px; } | |
| /* MODAL */ | |
| .overlay { position: fixed; inset: 0; background: rgba(0,0,0,.4); backdrop-filter: blur(4px); display: none; align-items: center; justify-content: center; z-index: 50; } | |
| .overlay.open { display: flex; } | |
| .modal { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px; width: 500px; max-width: 95vw; max-height: 90vh; overflow-y: auto; position: relative; box-shadow: var(--shadow-md); } | |
| .modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; } | |
| .modal-header h2 { font-size: 17px; font-weight: 700; letter-spacing: -.3px; } | |
| .modal-close { background: none; border: none; font-size: 20px; color: var(--text2); cursor: pointer; padding: 2px 6px; border-radius: 6px; line-height: 1; } | |
| .modal-close:hover { background: var(--bg); } | |
| .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; } | |
| .form-field { display: flex; flex-direction: column; gap: 5px; } | |
| .form-field.full { grid-column: 1 / -1; } | |
| .form-field label { font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; } | |
| .form-field input, .form-field select { padding: 9px 12px; font-size: 13px; border: 1px solid var(--border2); border-radius: var(--radius); outline: none; font-family: 'Inter', sans-serif; color: var(--text); background: var(--white); transition: border-color .12s; } | |
| .form-field input:focus, .form-field select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(91,78,248,.1); } | |
| .form-hint { font-size: 11px; color: var(--text3); font-family: 'JetBrains Mono', monospace; } | |
| .modal-footer { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--border); } | |
| /* TOAST */ | |
| .toast { position: fixed; bottom: 24px; right: 24px; background: var(--text); color: var(--white); border-radius: var(--radius); padding: 12px 18px; font-size: 13px; font-weight: 500; z-index: 200; transform: translateY(20px); opacity: 0; transition: all .25s; box-shadow: var(--shadow-md); } | |
| .toast.show { transform: translateY(0); opacity: 1; } | |
| .toast.success { background: #1a7a33; } | |
| .toast.error { background: var(--red); } | |
| /* PANEL */ | |
| .panel { display: none; } | |
| .panel.active { display: block; } | |
| /* ── DOCS PAGE ── */ | |
| .docs-wrap { max-width: 760px; } | |
| .docs-hero { | |
| background: var(--white); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-lg); | |
| padding: 28px 32px; | |
| margin-bottom: 20px; | |
| box-shadow: var(--shadow); | |
| } | |
| .docs-hero h2 { font-size: 22px; font-weight: 700; letter-spacing: -.4px; margin-bottom: 8px; } | |
| .docs-hero p { font-size: 14px; color: var(--text2); line-height: 1.7; } | |
| .docs-section { | |
| background: var(--white); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-lg); | |
| margin-bottom: 14px; | |
| box-shadow: var(--shadow); | |
| overflow: hidden; | |
| } | |
| .docs-section-header { | |
| padding: 16px 20px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| cursor: pointer; | |
| user-select: none; | |
| } | |
| .docs-section-header:hover { background: var(--bg); } | |
| .docs-section-icon { | |
| width: 36px; height: 36px; | |
| border-radius: 10px; | |
| display: flex; align-items: center; justify-content: center; | |
| font-size: 18px; | |
| flex-shrink: 0; | |
| } | |
| .docs-section-header h3 { font-size: 15px; font-weight: 600; letter-spacing: -.2px; flex: 1; } | |
| .docs-section-header .toggle { font-size: 12px; color: var(--text3); font-family: 'JetBrains Mono', monospace; } | |
| .docs-section-body { padding: 20px 20px 24px; display: none; } | |
| .docs-section-body.open { display: block; } | |
| .docs-section-body p { font-size: 14px; color: var(--text2); line-height: 1.75; margin-bottom: 14px; } | |
| .docs-section-body p:last-child { margin-bottom: 0; } | |
| .docs-section-body strong { color: var(--text); font-weight: 600; } | |
| .step-list { display: flex; flex-direction: column; gap: 0; } | |
| .step { display: flex; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--border); } | |
| .step:last-child { border-bottom: none; } | |
| .step-num { width: 26px; height: 26px; border-radius: 50%; background: var(--accent-light); color: var(--accent); font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 1px; } | |
| .step-body { flex: 1; } | |
| .step-title { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 3px; } | |
| .step-desc { font-size: 13px; color: var(--text2); line-height: 1.6; } | |
| .code-block { | |
| background: #1c1c1e; | |
| color: #e8e8ec; | |
| border-radius: var(--radius); | |
| padding: 14px 16px; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 12px; | |
| line-height: 1.8; | |
| margin: 12px 0; | |
| overflow-x: auto; | |
| } | |
| .code-block .comment { color: #636366; } | |
| .code-block .key { color: #bf85fa; } | |
| .code-block .val { color: #63d05a; } | |
| .rule-list { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; } | |
| .rule { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; color: var(--text2); line-height: 1.6; } | |
| .rule-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; margin-top: 6px; } | |
| .rule strong { color: var(--text); } | |
| .stage-flow { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 14px 0; } | |
| .stage-box { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; text-align: center; } | |
| .stage-box .stage-name { font-size: 12px; font-weight: 700; margin-bottom: 4px; } | |
| .stage-box .stage-dur { font-size: 11px; color: var(--text3); font-family: 'JetBrains Mono', monospace; } | |
| .stage-box.s1 { background: var(--accent-light); border-color: #c4b8fd; } | |
| .stage-box.s1 .stage-name { color: var(--accent); } | |
| .stage-box.s2 { background: var(--amber-light); border-color: #fcd47a; } | |
| .stage-box.s2 .stage-name { color: #7a4d00; } | |
| .stage-box.s3 { background: var(--amber-light); border-color: #fcd47a; } | |
| .stage-box.s3 .stage-name { color: #7a4d00; } | |
| .stage-box.s4 { background: var(--green-light); border-color: #86efac; } | |
| .stage-box.s4 .stage-name { color: #1a7a33; } | |
| .warn-table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 12px 0; } | |
| .warn-table th { text-align: left; padding: 8px 12px; font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; border-bottom: 2px solid var(--border); } | |
| .warn-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); color: var(--text2); vertical-align: top; line-height: 1.5; } | |
| .warn-table tr:last-child td { border-bottom: none; } | |
| .warn-table td:first-child { font-weight: 600; color: var(--text); white-space: nowrap; } | |
| .signal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 12px 0; } | |
| .signal-card { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; } | |
| .signal-card .signal-name { font-size: 12px; font-weight: 700; color: var(--text); margin-bottom: 4px; } | |
| .signal-card .signal-desc { font-size: 12px; color: var(--text2); line-height: 1.5; } | |
| .highlight-box { background: var(--accent-light); border: 1px solid #c4b8fd; border-radius: var(--radius); padding: 14px 16px; margin: 12px 0; font-size: 13px; color: #3730a3; line-height: 1.7; } | |
| .highlight-box strong { color: #2e27a0; } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- LOGIN --> | |
| <div id="login-screen"> | |
| <div class="login-card"> | |
| <h1>Intern Pipeline</h1> | |
| <p>Enter your password to continue</p> | |
| <input type="password" id="pw" placeholder="Password" onkeydown="if(event.key==='Enter')login()"> | |
| <button class="btn btn-primary" style="width:100%;padding:11px" onclick="login()">Sign in</button> | |
| <div class="login-error" id="login-err">Incorrect password</div> | |
| </div> | |
| </div> | |
| <!-- APP --> | |
| <div id="app"> | |
| <div class="layout"> | |
| <!-- SIDEBAR --> | |
| <div class="sidebar"> | |
| <div class="sidebar-brand"> | |
| <h2>Intern Pipeline</h2> | |
| <p>banao-tech</p> | |
| </div> | |
| <div class="nav-label">Management</div> | |
| <button class="nav-item active" onclick="showPanel('interns')" id="nav-interns">👥 All Interns</button> | |
| <div class="nav-label">Actions</div> | |
| <button class="nav-item" onclick="openAdd()">+ Add Intern</button> | |
| <div class="sidebar-footer"> | |
| API<br>banao-tech-interns-manager.hf.space | |
| </div> | |
| </div> | |
| <!-- MAIN --> | |
| <div class="main"> | |
| <div class="topbar"> | |
| <h1 id="panel-title">All Interns</h1> | |
| <div class="topbar-right"> | |
| <button class="btn" id="refresh-btn" onclick="load()" style="display:block">↻ Refresh</button> | |
| <button class="btn btn-primary" id="add-btn" onclick="openAdd()" style="display:block">+ Add Intern</button> | |
| </div> | |
| </div> | |
| <div class="content"> | |
| <!-- INTERNS PANEL --> | |
| <div class="panel active" id="panel-interns"> | |
| <div class="stats"> | |
| <div class="stat-card"><div class="label">Total Active</div><div class="value" id="s-total">—</div><div class="sub">all departments</div></div> | |
| <div class="stat-card"><div class="label">Week 0</div><div class="value" id="s-w0">—</div><div class="sub">onboarding</div></div> | |
| <div class="stat-card"><div class="label">Probation</div><div class="value" id="s-prob">—</div><div class="sub">W1 + W2</div></div> | |
| <div class="stat-card"><div class="label">Intern</div><div class="value" id="s-intern">—</div><div class="sub">regular</div></div> | |
| </div> | |
| <div class="filters"> | |
| <input type="text" id="q" placeholder="Search name or ID…" oninput="filter()"> | |
| <select id="f-dept" onchange="load()"> | |
| <option value="">All departments</option> | |
| <option value="ai">AI</option> | |
| <option value="hr">HR</option> | |
| <option value="sales">Sales</option> | |
| <option value="product">Product</option> | |
| <option value="general">General</option> | |
| </select> | |
| <select id="f-stage" onchange="filter()"> | |
| <option value="">All stages</option> | |
| <option value="week0">Week 0</option> | |
| <option value="intern">Intern</option> | |
| <option value="probation_w1">Probation W1</option> | |
| <option value="probation_w2">Probation W2</option> | |
| </select> | |
| </div> | |
| <div class="table-card"> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Name</th><th>Candidate ID</th><th>Department</th><th>Stage</th> | |
| <th>Status</th><th>Start Date</th><th>Misses</th><th>Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody id="tbody"> | |
| <tr><td colspan="8" style="text-align:center;padding:48px;color:var(--text3)">Loading…</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">2</div> | |
| <div class="step-body"> | |
| <div class="step-title">Probation Week 1 — Observe</div> | |
| <div class="step-desc">FT observes without intervening. Bot sends the FT a daily focus brief each morning and signal questions each evening. Signals logged: proactive start, thinking before building, debugging posture, comms rhythm, depth under questioning.</div> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">3</div> | |
| <div class="step-body"> | |
| <div class="step-title">Probation Week 2 — Hold</div> | |
| <div class="step-desc">FT holds the intern to commitments made on Day 6. Specific deliverables with specific dates. Bot tracks Wednesday and Friday deadlines. Miss a deadline without flagging it first → formal warning.</div> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">4</div> | |
| <div class="step-body"> | |
| <div class="step-title">Intern — Ongoing</div> | |
| <div class="step-desc">Regular interns still submit EOD reports. Same miss logic applies — miss one gets a warning, miss two and HR is notified. No elimination recommendation — HR follows up directly.</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION 2 --> | |
| <div class="docs-section"> | |
| <div class="docs-section-header" onclick="toggleSection('s2')"> | |
| <div class="docs-section-icon" style="background:#fff4e0">📋</div> | |
| <h3>EOD Report Format</h3> | |
| <span class="toggle" id="s2-toggle">▼ expand</span> | |
| </div> | |
| <div class="docs-section-body" id="s2-body"> | |
| <p>Every intern posts their EOD report in their department Slack channel. The message <strong>must start with EOD: or REPORT:</strong> — otherwise the bot ignores it.</p> | |
| <div class="code-block"> | |
| <span class="key">EOD:</span> | |
| ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| <span class="val">EOD REPORT — Your Name — DD Mon YYYY</span> | |
| ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| <span class="key">TASKS COMPLETED</span> | |
| <span class="comment">- Task 1 with PR/ticket ID if any</span> | |
| <span class="comment">- Task 2</span> | |
| <span class="key">TASKS IN PROGRESS</span> | |
| <span class="comment">- What you started but didn't finish</span> | |
| <span class="comment">- Expected completion: tomorrow / specific time</span> | |
| <span class="key">BLOCKERS</span> | |
| <span class="comment">- What is stopping you / None</span> | |
| <span class="key">WHAT I LEARNED TODAY</span> | |
| <span class="comment">- Concept or tool you understood today</span> | |
| <span class="key">AI USAGE TODAY</span> | |
| <span class="comment">Tool used: Claude / ChatGPT / Copilot</span> | |
| <span class="comment">Chat link: https://...</span> | |
| <span class="key">PLAN FOR TOMORROW</span> | |
| <span class="comment">- Task 1 with expected output</span> | |
| <span class="key">CONFIDENCE LEVEL TODAY</span> | |
| <span class="comment">> [x] On track [ ] Crushing it [ ] Need help [ ] Stuck</span> | |
| ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</div> | |
| <p>The bot uses Claude to extract all fields from the report. If any required field is missing, it replies with exactly which fields are missing and the correct format.</p> | |
| <div class="highlight-box"> | |
| <strong>The AI chat link is required.</strong> Interns must share the actual conversation link so their thinking can be verified. If they used AI but have no link, that is flagged as a quality issue. | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION 3 --> | |
| <div class="docs-section"> | |
| <div class="docs-section-header" onclick="toggleSection('s3')"> | |
| <div class="docs-section-icon" style="background:#ffe5e3">⚠️</div> | |
| <h3>Miss and Warning Logic</h3> | |
| <span class="toggle" id="s3-toggle">▼ expand</span> | |
| </div> | |
| <div class="docs-section-body" id="s3-body"> | |
| <p>The bot checks for missed reports 30 minutes after each report window closes. Before firing a warning, it checks the intern's Slack status — if they have set a leave-related status, the miss is skipped.</p> | |
| <p><strong>Leave keywords detected:</strong> leave, sick, away, ooo, out of office, on leave, medical, emergency, holiday, unavailable, off today.</p> | |
| <table class="warn-table"> | |
| <thead><tr><th>Stage</th><th>Miss 1</th><th>Miss 2</th></tr></thead> | |
| <tbody> | |
| <tr> | |
| <td>Week 0</td> | |
| <td>Warning posted in channel. References commitment. States consequence.</td> | |
| <td>Elimination recommendation sent to HR. HR confirms before outcome is posted.</td> | |
| </tr> | |
| <tr> | |
| <td>Probation W1 / W2</td> | |
| <td>Warning posted. Logged as a signal flag in the evaluation.</td> | |
| <td>Formal warning posted. FT and HR notified via DM. Reflected in end-of-week eval.</td> | |
| </tr> | |
| <tr> | |
| <td>Intern</td> | |
| <td>Warning posted in channel. Softer tone.</td> | |
| <td>HR notified via DM. No elimination — HR follows up directly.</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <p><strong>Sundays are skipped.</strong> No warnings fire on Sunday. Day numbers also skip Sundays — so a week is 6 working days, not 7.</p> | |
| </div> | |
| </div> | |
| <!-- SECTION 4 --> | |
| <div class="docs-section"> | |
| <div class="docs-section-header" onclick="toggleSection('s4')"> | |
| <div class="docs-section-icon" style="background:#e8f8ec">📊</div> | |
| <h3>Evaluations — What the Bot Reads</h3> | |
| <span class="toggle" id="s4-toggle">▼ expand</span> | |
| </div> | |
| <div class="docs-section-body" id="s4-body"> | |
| <p>At the end of each stage, the bot generates a full evaluation using Claude. It reads all reports, all FT signal answers, and prior eval outputs — and produces a recommendation.</p> | |
| <p><strong>Week 0 signals (4):</strong></p> | |
| <div class="signal-grid"> | |
| <div class="signal-card"><div class="signal-name">Report cadence</div><div class="signal-desc">Did they submit on time without being chased?</div></div> | |
| <div class="signal-card"><div class="signal-name">Technical engagement</div><div class="signal-desc">Are reports showing real AI tool usage or surface-level activity?</div></div> | |
| <div class="signal-card"><div class="signal-name">Honesty</div><div class="signal-desc">Do they admit blockers? Any signs of fabricated progress?</div></div> | |
| <div class="signal-card"><div class="signal-name">Friction response</div><div class="signal-desc">When tools broke or APIs failed — what did they do?</div></div> | |
| </div> | |
| <p style="margin-top:14px"><strong>Probation W1 signals (5):</strong></p> | |
| <div class="signal-grid"> | |
| <div class="signal-card"><div class="signal-name">Proactive start</div><div class="signal-desc">Clarifying questions Day 1, architecture Day 2 without being asked.</div></div> | |
| <div class="signal-card"><div class="signal-name">Thinking before building</div><div class="signal-desc">Documented plan before code. Trade-offs named. Failure cases considered.</div></div> | |
| <div class="signal-card"><div class="signal-name">Debugging posture</div><div class="signal-desc">Blockers surfaced with specific detail and prior attempts.</div></div> | |
| <div class="signal-card"><div class="signal-name">Comms rhythm</div><div class="signal-desc">Daily reports on cadence showing real reflection, not just activity.</div></div> | |
| <div class="signal-card" style="grid-column:1/-1"><div class="signal-name">Depth under questioning</div><div class="signal-desc">Day 4 check — explained decisions, held reasoning under pushback, knew where system fails.</div></div> | |
| </div> | |
| <p style="margin-top:14px"><strong>Probation W2 signals (6):</strong> delivery reliability, proactive comms, ownership of hard parts, depth under questioning, end-of-probation document quality, and two-week trajectory.</p> | |
| <div class="highlight-box"> | |
| <strong>HR confirms before anything is communicated.</strong> Every evaluation recommendation — offer, no offer, extend, end relationship — goes to HR first. The bot never announces an outcome without HR approval. | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION 5 --> | |
| <div class="docs-section"> | |
| <div class="docs-section-header" onclick="toggleSection('s5')"> | |
| <div class="docs-section-icon" style="background:#ede9fe">⏰</div> | |
| <h3>Automated Schedule — What Fires When</h3> | |
| <span class="toggle" id="s5-toggle">▼ expand</span> | |
| </div> | |
| <div class="docs-section-body" id="s5-body"> | |
| <table class="warn-table"> | |
| <thead><tr><th>Time (IST)</th><th>What happens</th></tr></thead> | |
| <tbody> | |
| <tr><td>9:00 AM</td><td>FT receives daily focus brief for each probation intern they manage</td></tr> | |
| <tr><td>12:30 PM</td><td>Missed report check — noon window closes</td></tr> | |
| <tr><td>5:30 PM</td><td>Missed report check — 5pm window closes</td></tr> | |
| <tr><td>6:00 PM</td><td>FT receives signal questions for today's evaluation</td></tr> | |
| <tr><td>6:30 PM (Wed)</td><td>Wednesday deliverable deadline check — W2 interns</td></tr> | |
| <tr><td>12:00 PM (Fri)</td><td>Friday deliverable deadline check — W2 interns</td></tr> | |
| <tr><td>8:00 PM</td><td>End-of-stage evaluation generated on Day 5 and Day 10</td></tr> | |
| <tr><td>10:30 PM</td><td>Missed report check — 10pm window closes</td></tr> | |
| </tbody> | |
| </table> | |
| <p>All jobs skip Sunday automatically. Day numbers also skip Sunday in the count.</p> | |
| </div> | |
| </div> | |
| <!-- SECTION 6 --> | |
| <div class="docs-section"> | |
| <div class="docs-section-header" onclick="toggleSection('s6')"> | |
| <div class="docs-section-icon" style="background:#fff4e0">👤</div> | |
| <h3>FT Responsibilities in This System</h3> | |
| <span class="toggle" id="s6-toggle">▼ expand</span> | |
| </div> | |
| <div class="docs-section-body" id="s6-body"> | |
| <p>The bot handles tracking and logistics. The FT handles judgment and accountability. Here is what the FT does and does not do.</p> | |
| <div class="rule-list"> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>Morning brief (9 AM):</strong> The bot sends the FT a focus note for the day — what to observe, what not to do. The FT reads it and acts accordingly.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>Evening signal questions (6 PM):</strong> The bot asks 2–3 specific questions about today's intern behaviour. FT replies in the DM. Bot logs the answers as signals.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>Day 4 depth check:</strong> FT runs a 15-minute check-in with the intern. Bot pre-generates the questions based on the architecture the intern submitted on Day 2.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>Day 6 commitments:</strong> FT sets specific deliverables with specific dates. Bot logs them. Bot enforces them on Wednesday EOD and Friday noon.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>Do NOT coach report quality mid-week.</strong> The bot notes quality. The FT does not comment on it. That removes the signal the report quality provides.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>Do NOT chase missed reports.</strong> The bot handles warnings. The FT chasing defeats the purpose of the discipline filter.</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION 7 --> | |
| <div class="docs-section"> | |
| <div class="docs-section-header" onclick="toggleSection('s7')"> | |
| <div class="docs-section-icon" style="background:#e8f8ec">🏢</div> | |
| <h3>Adding a New Department</h3> | |
| <span class="toggle" id="s7-toggle">▼ expand</span> | |
| </div> | |
| <div class="docs-section-body" id="s7-body"> | |
| <p>The system supports multiple departments. Each department gets its own Slack channel. All interns across all departments are tracked in the same Google Sheet.</p> | |
| <div class="step-list"> | |
| <div class="step"> | |
| <div class="step-num">1</div> | |
| <div class="step-body"> | |
| <div class="step-title">Create a Slack channel</div> | |
| <div class="step-desc">e.g. #sales-eod or #hr-eod. Invite the intern-management-agent bot to it.</div> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">2</div> | |
| <div class="step-body"> | |
| <div class="step-title">Get the channel ID</div> | |
| <div class="step-desc">Right-click the channel in Slack → View channel details → Copy ID at the bottom (starts with C).</div> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">3</div> | |
| <div class="step-body"> | |
| <div class="step-title">Add interns via this dashboard</div> | |
| <div class="step-desc">Use the Add Intern button. Set department to the new department name. Set channel_id to the channel ID from step 2. The bot will start listening to that channel immediately.</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION 8 --> | |
| <div class="docs-section"> | |
| <div class="docs-section-header" onclick="toggleSection('s8')"> | |
| <div class="docs-section-icon" style="background:#ffe5e3">🗄️</div> | |
| <h3>What Is Stored in Google Sheets</h3> | |
| <span class="toggle" id="s8-toggle">▼ expand</span> | |
| </div> | |
| <div class="docs-section-body" id="s8-body"> | |
| <p>All data lives in a Google Sheet with 7 tabs. Nothing is deleted — only marked as eliminated or cleared. This gives you a full audit trail.</p> | |
| <div class="rule-list"> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>candidates</strong> — every intern's full record, stage, status, miss count.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>reports_log</strong> — every EOD report submitted, with timestamp, quality score, and flags.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>signals_log</strong> — every FT signal answer logged per day per intern.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>warnings_log</strong> — every warning issued, when, what triggered it, exact message sent.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>commitments</strong> — Week 2 deliverables, due dates, whether delivered on time.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>evaluations</strong> — end-of-stage evaluations with signals, recommendations, HR confirmation status.</div></div> | |
| <div class="rule"><div class="rule-dot"></div><div><strong>decisions_log</strong> — every final decision made — offer, elimination, extension — with who confirmed it and when.</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ADD / EDIT MODAL --> | |
| <div class="overlay" id="modal-overlay" onclick="if(event.target===this)closeModal()"> | |
| <div class="modal"> | |
| <div class="modal-header"> | |
| <h2 id="modal-title">Add Intern</h2> | |
| <button class="modal-close" onclick="closeModal()">×</button> | |
| </div> | |
| <input type="hidden" id="m-id"> | |
| <div class="form-grid"> | |
| <div class="form-field full"><label>Full Name *</label><input type="text" id="m-name" placeholder="Roshesh Shah"></div> | |
| <div class="form-field"><label>Slack User ID *</label><input type="text" id="m-slack" placeholder="U0AFNM2H88G"><span class="form-hint">Profile → More → Copy member ID</span></div> | |
| <div class="form-field"><label>Department</label><input type="text" id="m-dept" placeholder="e.g. AI, HR, Sales"><span class="form-hint">Type your department name</span></div> | |
| <div class="form-field"><label>Stage</label><select id="m-stage"><option value="week0">Week 0</option><option value="intern">Regular Intern</option><option value="probation_w1">Probation W1</option><option value="probation_w2">Probation W2</option></select></div> | |
| <div class="form-field"><label>Cohort / Batch</label><input type="text" id="m-cohort" placeholder="BATCH_001"></div> | |
| <div class="form-field"><label>Start Date *</label><input type="date" id="m-start"></div> | |
| <div class="form-field"><label>FT / Mentor Slack ID *</label><input type="text" id="m-ft" placeholder="U083B055EA0"></div> | |
| <div class="form-field"><label>HR Slack ID *</label><input type="text" id="m-hr" placeholder="U083B055EA0"></div> | |
| <div class="form-field full"><label>Channel ID</label><input type="text" id="m-channel" placeholder="C08L1CFEF40"><span class="form-hint">Right-click channel → View details → Copy ID</span></div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button class="btn" onclick="closeModal()">Cancel</button> | |
| <button class="btn btn-primary" id="m-submit" onclick="submit()">Add Intern</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- CONFIRM DELETE --> | |
| <div class="overlay" id="confirm-overlay" onclick="if(event.target===this)closeConfirm()"> | |
| <div class="modal" style="width:380px"> | |
| <div class="modal-header"> | |
| <h2>Remove Intern</h2> | |
| <button class="modal-close" onclick="closeConfirm()">×</button> | |
| </div> | |
| <p style="font-size:14px;color:var(--text2);line-height:1.6">Remove <strong id="confirm-name" style="color:var(--text)"></strong> from the pipeline?</p> | |
| <p style="font-size:12px;color:var(--text3);margin-top:8px">Their record is kept for audit. Status is set to eliminated.</p> | |
| <div class="modal-footer"> | |
| <button class="btn" onclick="closeConfirm()">Cancel</button> | |
| <button class="btn" style="background:var(--red);border-color:var(--red);color:#fff" onclick="confirmDelete()">Remove</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="toast" id="toast"></div> | |
| <script> | |
| const API = 'https://banao-tech-interns-manager.hf.space'; | |
| const PASSWORD = 'banao2026'; | |
| let allInterns = [], deleteId = null; | |
| function login() { | |
| if (document.getElementById('pw').value === PASSWORD) { | |
| document.getElementById('login-screen').style.display = 'none'; | |
| document.getElementById('app').style.display = 'block'; | |
| load(); | |
| } else { | |
| document.getElementById('login-err').style.display = 'block'; | |
| document.getElementById('pw').value = ''; | |
| } | |
| } | |
| document.getElementById('pw').addEventListener('keydown', e => { if(e.key==='Enter') login(); }); | |
| function toast(msg, type='') { | |
| const el = document.getElementById('toast'); | |
| el.textContent = msg; | |
| el.className = `toast ${type} show`; | |
| setTimeout(() => el.className = 'toast', 3000); | |
| } | |
| async function api(path, opts={}) { | |
| const r = await fetch(API + path, { headers: {'Content-Type':'application/json'}, ...opts }); | |
| if (!r.ok) throw new Error(`HTTP ${r.status}`); | |
| return r.json(); | |
| } | |
| async function load() { | |
| const dept = document.getElementById('f-dept').value; | |
| const url = dept ? `/interns?department=${dept}` : '/interns'; | |
| try { | |
| const data = await api(url); | |
| allInterns = data.interns || []; | |
| updateStats(); filter(); | |
| } catch(e) { | |
| document.getElementById('tbody').innerHTML = | |
| `<tr><td colspan="8"><div class="empty-state"><div class="icon">⚠️</div>Could not load. Check API or CORS.</div></td></tr>`; | |
| } | |
| } | |
| function updateStats() { | |
| document.getElementById('s-total').textContent = allInterns.length; | |
| document.getElementById('s-w0').textContent = allInterns.filter(i=>i.stage==='week0').length; | |
| document.getElementById('s-prob').textContent = allInterns.filter(i=>i.stage?.includes('probation')).length; | |
| document.getElementById('s-intern').textContent = allInterns.filter(i=>i.stage==='intern').length; | |
| } | |
| function filter() { | |
| const q = document.getElementById('q').value.toLowerCase(); | |
| const stage = document.getElementById('f-stage').value; | |
| render(allInterns.filter(i => { | |
| const mq = !q || (i.name||'').toLowerCase().includes(q) || (i.candidate_id||'').toLowerCase().includes(q); | |
| return mq && (!stage || i.stage === stage); | |
| })); | |
| } | |
| function stageLabel(s) { return {week0:'Week 0',intern:'Intern',probation_w1:'Prob W1',probation_w2:'Prob W2'}[s]||s||'—'; } | |
| function missClass(n) { n=parseInt(n)||0; return n===0?'miss-0':n===1?'miss-1':'miss-2'; } | |
| function statusCls(s) { if(!s||s==='active') return ''; return s.replace(/_/g,'-'); } | |
| function render(interns) { | |
| const tbody = document.getElementById('tbody'); | |
| if (!interns.length) { tbody.innerHTML=`<tr><td colspan="8"><div class="empty-state"><div class="icon">○</div>No interns found</div></td></tr>`; return; } | |
| tbody.innerHTML = interns.map(i=>` | |
| <tr> | |
| <td class="name">${i.name||'—'}</td> | |
| <td style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text3)">${i.candidate_id||'—'}</td> | |
| <td style="text-transform:uppercase;font-size:12px">${i.department||'general'}</td> | |
| <td><span class="pill pill-${i.stage||'intern'}">${stageLabel(i.stage)}</span></td> | |
| <td><span class="status-badge ${statusCls(i.status)}">${i.status||'active'}</span></td> | |
| <td style="font-family:'JetBrains Mono',monospace;font-size:12px">${i.week0_start_date||'—'}</td> | |
| <td><span class="miss-count ${missClass(i.miss_count)}">${i.miss_count??0}</span></td> | |
| <td><div class="actions"> | |
| <button class="btn btn-sm" onclick='openEdit(${JSON.stringify(i)})'>Edit</button> | |
| <button class="btn btn-sm" style="color:var(--red);border-color:var(--red)" onclick="openConfirm('${i.candidate_id}','${(i.name||'').replace(/'/g,"\\'")}')">Remove</button> | |
| </div></td> | |
| </tr>`).join(''); | |
| } | |
| function showPanel(id) { | |
| document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active')); | |
| document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active')); | |
| document.getElementById('panel-'+id).classList.add('active'); | |
| document.getElementById('nav-'+id).classList.add('active'); | |
| const titles = {interns:'All Interns'}; | |
| document.getElementById('panel-title').textContent = titles[id]||id; | |
| } | |
| function toggleSection(id) { | |
| const body = document.getElementById(id+'-body'); | |
| const toggle = document.getElementById(id+'-toggle'); | |
| const isOpen = body.classList.contains('open'); | |
| body.classList.toggle('open', !isOpen); | |
| toggle.textContent = isOpen ? '▼ expand' : '▲ collapse'; | |
| } | |
| function openAdd() { | |
| showPanel('interns'); | |
| document.getElementById('modal-title').textContent='Add Intern'; | |
| document.getElementById('m-submit').textContent='Add Intern'; | |
| document.getElementById('m-id').value=''; | |
| ['m-name','m-slack','m-ft','m-hr','m-channel'].forEach(id=>document.getElementById(id).value=''); | |
| document.getElementById('m-dept').value=''; | |
| document.getElementById('m-stage').value='week0'; | |
| document.getElementById('m-cohort').value='BATCH_001'; | |
| document.getElementById('m-start').value=new Date().toISOString().split('T')[0]; | |
| document.getElementById('modal-overlay').classList.add('open'); | |
| setTimeout(()=>document.getElementById('m-name').focus(),100); | |
| } | |
| function openEdit(i) { | |
| document.getElementById('modal-title').textContent='Edit Intern'; | |
| document.getElementById('m-submit').textContent='Save Changes'; | |
| document.getElementById('m-id').value=i.candidate_id; | |
| document.getElementById('m-name').value=i.name||''; | |
| document.getElementById('m-slack').value=i.slack_user_id||''; | |
| document.getElementById('m-dept').value=i.department||''; | |
| document.getElementById('m-stage').value=i.stage||'intern'; | |
| document.getElementById('m-cohort').value=i.cohort_id||''; | |
| document.getElementById('m-start').value=i.week0_start_date||''; | |
| document.getElementById('m-ft').value=i.ft_slack_id||''; | |
| document.getElementById('m-hr').value=i.hr_slack_id||''; | |
| document.getElementById('m-channel').value=i.channel_id||''; | |
| document.getElementById('modal-overlay').classList.add('open'); | |
| } | |
| function closeModal() { document.getElementById('modal-overlay').classList.remove('open'); } | |
| async function submit() { | |
| const cid = document.getElementById('m-id').value; | |
| const isEdit = !!cid; | |
| const name = document.getElementById('m-name').value.trim(); | |
| const slack = document.getElementById('m-slack').value.trim(); | |
| const start = document.getElementById('m-start').value; | |
| const ft = document.getElementById('m-ft').value.trim(); | |
| const hr = document.getElementById('m-hr').value.trim(); | |
| if (!name || (!isEdit && (!slack||!start||!ft||!hr))) { toast('Fill in all required fields','error'); return; } | |
| const btn = document.getElementById('m-submit'); | |
| btn.disabled=true; btn.textContent=isEdit?'Saving…':'Adding…'; | |
| try { | |
| const payload = { name, stage:document.getElementById('m-stage').value, department:document.getElementById('m-dept').value, cohort_id:document.getElementById('m-cohort').value.trim()||'BATCH_001', week0_start_date:start, ft_slack_id:ft, hr_slack_id:hr, channel_id:document.getElementById('m-channel').value.trim() }; | |
| if (!isEdit) { payload.slack_user_id=slack; } | |
| const data = await api(isEdit?`/interns/${cid}`:`/admin/add_candidate`, { method:isEdit?'PATCH':'POST', body:JSON.stringify(payload) }); | |
| if (data.ok) { | |
| closeModal(); | |
| toast(isEdit?`${name} updated ✓`:`${name} added ✓`,'success'); | |
| await load(); // reload from API so next edit shows fresh data | |
| } | |
| else toast(data.error||'Something went wrong','error'); | |
| } catch(e) { toast('Request failed','error'); } | |
| btn.disabled=false; btn.textContent=isEdit?'Save Changes':'Add Intern'; | |
| } | |
| function openConfirm(id,name) { deleteId=id; document.getElementById('confirm-name').textContent=name; document.getElementById('confirm-overlay').classList.add('open'); } | |
| function closeConfirm() { deleteId=null; document.getElementById('confirm-overlay').classList.remove('open'); } | |
| async function confirmDelete() { | |
| if (!deleteId) return; | |
| try { | |
| const data = await api(`/interns/${deleteId}`,{method:'DELETE'}); | |
| if (data.ok) { closeConfirm(); toast('Intern removed ✓','success'); load(); } | |
| else toast(data.error||'Could not remove','error'); | |
| } catch(e) { toast('Request failed','error'); } | |
| } | |
| </script> | |
| </body> | |
| </html> |