SurveyVista/Web/Areas/Admin/Views/SurveyAnalysis/BatchAnalysisProgress.cshtml
2026-03-07 02:37:33 +01:00

513 lines
32 KiB
Text

@* Views/Admin/SurveyAnalysis/BatchAnalysisProgress.cshtml *@
@{
ViewData["Title"] = $"Batch Analysis — {ViewBag.QuestionnaireName}";
}
@section Styles {
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
<style>
@@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
:root{
--neon-blue:#60a5fa;--neon-purple:#c084fc;--neon-green:#34d399;--neon-pink:#f472b6;
--neon-yellow:#fbbf24;--neon-red:#f87171;--neon-cyan:#22d3ee;--neon-indigo:#818cf8;
--neon-teal:#33b3ae;--neon-amber:#f59e0b;--neon-orange:#fb923c;
--dark-900:#0f172a;--dark-800:#1e293b;--dark-700:#334155;--dark-600:#475569;
--dark-500:#64748b;--dark-400:#94a3b8;--dark-300:#cbd5e1;--dark-200:#e2e8f0;
--glass-bg:rgba(255,255,255,0.04);--glass-border:rgba(255,255,255,0.08);
--font-main:'Space Grotesk',sans-serif;--font-mono:'JetBrains Mono',monospace;
}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:var(--font-main);background:var(--dark-900);color:#e2e8f0;overflow-x:hidden}
.nex-bg{position:fixed;inset:0;z-index:-1;overflow:hidden}
.nex-bg .grid{position:absolute;inset:0;background-image:linear-gradient(rgba(34,211,238,0.04) 1px,transparent 1px),linear-gradient(90deg,rgba(34,211,238,0.04) 1px,transparent 1px);background-size:60px 60px;animation:gridDrift 25s linear infinite}
.nex-bg .mesh{position:absolute;inset:0;background:radial-gradient(ellipse at 25% 20%,rgba(34,211,238,0.06) 0%,transparent 55%),radial-gradient(ellipse at 75% 60%,rgba(96,165,250,0.05) 0%,transparent 55%),radial-gradient(ellipse at 50% 90%,rgba(192,132,252,0.04) 0%,transparent 55%);animation:meshFloat 18s ease-in-out infinite}
@@keyframes gridDrift{0%{transform:translate(0,0)}100%{transform:translate(60px,60px)}}
@@keyframes meshFloat{0%,100%{filter:hue-rotate(0deg);transform:scale(1)}50%{filter:hue-rotate(12deg);transform:scale(1.02)}}
@@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(1.12)}}
@@keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}
@@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
@@keyframes checkPop{0%{transform:scale(1)}50%{transform:scale(1.25)}100%{transform:scale(1)}}
@@keyframes progressShimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}
@@keyframes barGrow{from{width:0}to{width:var(--w)}}
.container{max-width:1400px;margin:0 auto;padding:0 2rem}
/* ===== HEADER ===== */
.page-header{position:relative;z-index:10;padding:2rem 0 1.5rem;border-bottom:1px solid rgba(34,211,238,0.12)}
.breadcrumb-nex{display:flex;align-items:center;gap:.7rem;margin-bottom:1.8rem;font-family:var(--font-mono);font-size:.65rem;letter-spacing:.08em;flex-wrap:wrap}
.breadcrumb-nex a{color:var(--dark-400);text-decoration:none;display:flex;align-items:center;gap:.4rem;transition:color .2s}
.breadcrumb-nex a:hover{color:var(--neon-cyan)}
.breadcrumb-nex .sep{color:var(--dark-600);font-size:.5rem}
.breadcrumb-nex .current{color:var(--neon-cyan);font-weight:600}
.header-row{display:flex;justify-content:space-between;align-items:flex-start;gap:2rem}
.header-left{flex:1}
.header-badge{display:inline-flex;align-items:center;gap:.5rem;padding:.4rem 1rem;background:rgba(34,211,238,0.08);border:1px solid rgba(34,211,238,0.2);border-radius:50px;margin-bottom:1.5rem;font-family:var(--font-mono);font-size:.6rem;font-weight:600;letter-spacing:.1em;color:var(--neon-cyan)}
.header-badge .dot{width:6px;height:6px;background:var(--neon-cyan);border-radius:50%;animation:pulse 2s infinite}
.header-title{font-size:2.8rem;font-weight:700;line-height:1.1;margin-bottom:.8rem;color:#fff}
.header-title .grad{background:linear-gradient(135deg,var(--neon-cyan),var(--neon-blue),var(--neon-purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.header-sub{font-size:.95rem;color:var(--dark-300);line-height:1.6}
.header-actions{display:flex;flex-wrap:wrap;gap:.7rem;align-items:flex-start;padding-top:.5rem}
.h-btn{display:flex;align-items:center;gap:.6rem;padding:.65rem 1.2rem;border-radius:10px;text-decoration:none;font-family:var(--font-mono);font-size:.68rem;font-weight:600;letter-spacing:.04em;transition:all .25s;border:1px solid;cursor:pointer;background:none}
.h-btn:hover{transform:translateY(-2px);text-decoration:none}
.h-btn.sec{background:rgba(255,255,255,0.04);border-color:rgba(255,255,255,0.1);color:var(--dark-300)}
.h-btn.sec:hover{background:rgba(34,211,238,0.08);border-color:rgba(34,211,238,0.25);color:var(--neon-cyan)}
/* ===== MAIN CARD ===== */
.nex-card{background:rgba(30,41,59,0.45);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.07);border-radius:18px;overflow:hidden;position:relative;margin-bottom:1.5rem}
/* Processing Header */
.proc-head{background:linear-gradient(135deg,rgba(30,41,59,0.95),rgba(51,65,85,0.95));padding:1.5rem 1.8rem;display:flex;align-items:center;justify-content:space-between;gap:1rem;border-bottom:1px solid rgba(34,211,238,0.15);position:relative;flex-wrap:wrap}
.proc-head::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(34,211,238,0.08) 0%,transparent 60%)}
.proc-head-info{position:relative;z-index:2;flex:1}
.proc-head h3{font-family:var(--font-mono);font-size:.95rem;font-weight:700;letter-spacing:.05em;color:#e2e8f0;display:flex;align-items:center;gap:.8rem;margin-bottom:.3rem}
.proc-head p{font-size:.75rem;color:var(--dark-400)}
.status-chip{position:relative;z-index:2;display:flex;align-items:center;gap:.5rem;padding:.45rem 1rem;border-radius:20px;font-family:var(--font-mono);font-size:.6rem;font-weight:600;letter-spacing:.08em;border:1px solid;transition:all .3s}
.status-chip.init{background:rgba(255,255,255,0.05);border-color:rgba(255,255,255,0.15);color:var(--dark-300)}
.status-chip.processing{background:rgba(251,191,36,0.12);border-color:rgba(251,191,36,0.3);color:var(--neon-yellow)}
.status-chip.success{background:rgba(52,211,153,0.12);border-color:rgba(52,211,153,0.3);color:var(--neon-green)}
.status-chip.error{background:rgba(248,113,113,0.12);border-color:rgba(248,113,113,0.3);color:var(--neon-red)}
/* Progress */
.prog-section{padding:1.8rem}
.prog-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem}
.prog-label{font-weight:600;color:#fff;font-size:.95rem}
.prog-pct{font-family:var(--font-mono);font-weight:700;font-size:1.1rem;color:var(--neon-cyan)}
.prog-bar-wrap{position:relative;margin-bottom:1rem}
.prog-bar{height:16px;background:rgba(255,255,255,0.06);border-radius:8px;overflow:hidden;position:relative}
.prog-fill{height:100%;border-radius:8px;transition:width .4s ease;position:relative;overflow:hidden}
.prog-fill.animated{background:linear-gradient(90deg,var(--neon-cyan) 0%,var(--neon-blue) 25%,var(--neon-cyan) 50%,var(--neon-blue) 75%,var(--neon-cyan) 100%);background-size:200% 100%;animation:progressShimmer 2s linear infinite}
.prog-fill.complete{background:linear-gradient(90deg,var(--neon-green),var(--neon-teal));animation:none}
.prog-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-family:var(--font-mono);font-size:.65rem;font-weight:700;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.5)}
.prog-info{display:flex;justify-content:space-between;align-items:center}
.prog-count,.prog-eta{font-family:var(--font-mono);font-size:.7rem;color:var(--dark-400)}
/* Pipeline + Stats Grid */
.pipe-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;padding:0 1.8rem 1.8rem}
.pipe-box{background:rgba(255,255,255,0.025);border:1px solid rgba(255,255,255,0.06);border-radius:14px;padding:1.4rem}
.pipe-title{font-family:var(--font-mono);font-size:.78rem;font-weight:600;letter-spacing:.05em;color:#fff;display:flex;align-items:center;gap:.8rem;margin-bottom:1.2rem}
.pipe-title i{color:var(--neon-cyan)}
.pipe-step{padding:.6rem 0;display:flex;align-items:center;gap:.8rem;color:var(--dark-500);transition:all .3s;font-size:.78rem}
.pipe-step.active{color:var(--neon-cyan)}
.pipe-step.done{color:var(--neon-green)}
.pipe-step .s-icon{width:18px;text-align:center;font-size:.7rem}
.pipe-step.active .s-icon{animation:spin 1s linear infinite}
.pipe-step.done .s-icon{animation:checkPop .4s ease}
.stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}
.stat-card{background:rgba(255,255,255,0.025);border:1px solid rgba(255,255,255,0.06);border-radius:12px;padding:1.2rem;text-align:center;transition:all .2s}
.stat-card:hover{background:rgba(255,255,255,0.04);transform:translateY(-2px)}
.stat-icon{font-size:1.2rem;margin-bottom:.6rem}
.stat-card.pos .stat-icon{color:var(--neon-green)}.stat-card.neg .stat-icon{color:var(--neon-red)}
.stat-card.low .stat-icon{color:var(--neon-green)}.stat-card.high .stat-icon{color:var(--neon-red)}
.stat-val{font-family:var(--font-mono);font-size:1.5rem;font-weight:700;color:#fff;margin-bottom:.3rem;line-height:1}
.stat-lbl{font-family:var(--font-mono);font-size:.55rem;color:var(--dark-400);text-transform:uppercase;letter-spacing:.08em}
/* Control Footer */
.ctrl-foot{padding:1.2rem 1.8rem;background:rgba(15,23,42,0.4);border-top:1px solid rgba(255,255,255,0.06);display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap}
.ctrl-status{display:flex;align-items:center;gap:.7rem;color:var(--dark-300);font-size:.78rem;flex:1}
.ctrl-status i{color:var(--neon-cyan);font-size:.8rem}
.ctrl-btns{display:flex;gap:.7rem}
.btn-go{display:flex;align-items:center;gap:.5rem;padding:.6rem 1.4rem;border-radius:8px;font-family:var(--font-mono);font-size:.65rem;font-weight:600;letter-spacing:.05em;cursor:pointer;transition:all .25s;border:1px solid;text-decoration:none}
.btn-go:hover{transform:translateY(-1px)}
.btn-go.green{background:rgba(52,211,153,0.1);border-color:rgba(52,211,153,0.3);color:var(--neon-green)}
.btn-go.green:hover{background:rgba(52,211,153,0.2)}
.btn-go.red{background:rgba(248,113,113,0.1);border-color:rgba(248,113,113,0.3);color:var(--neon-red)}
.btn-go.red:hover{background:rgba(248,113,113,0.2)}
.btn-go.pri{background:linear-gradient(135deg,var(--neon-indigo),var(--neon-purple));border-color:transparent;color:#fff}
.btn-go.pri:hover{box-shadow:0 10px 24px rgba(129,140,248,0.25);color:#fff}
.btn-go.cyan{background:linear-gradient(135deg,var(--neon-cyan),var(--neon-blue));border-color:transparent;color:#fff}
.btn-go.cyan:hover{box-shadow:0 10px 24px rgba(34,211,238,0.25);color:#fff}
.btn-go:disabled{opacity:.4;cursor:not-allowed;transform:none}
/* ===== RESULTS ===== */
.results-head{background:linear-gradient(135deg,rgba(30,41,59,0.95),rgba(51,65,85,0.95));padding:1.5rem 1.8rem;display:flex;align-items:center;gap:1rem;border-bottom:1px solid rgba(52,211,153,0.15);position:relative}
.results-head::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(52,211,153,0.08) 0%,transparent 60%)}
.results-head h3{font-family:var(--font-mono);font-size:.95rem;font-weight:700;letter-spacing:.05em;color:#e2e8f0;display:flex;align-items:center;gap:.8rem;position:relative;z-index:2}
.results-head .r-icon{width:40px;height:40px;background:linear-gradient(135deg,var(--neon-green),var(--neon-teal));border-radius:10px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:1rem;position:relative;z-index:2;flex-shrink:0}
.results-body{padding:1.8rem}
.results-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1.2rem;margin-bottom:1.8rem}
.r-stat{text-align:center}
.r-stat-icon{width:50px;height:50px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.2rem;color:#fff;margin:0 auto .8rem}
.r-stat-icon.blue{background:linear-gradient(135deg,var(--neon-indigo),var(--neon-blue))}
.r-stat-icon.red{background:linear-gradient(135deg,var(--neon-red),var(--neon-amber))}
.r-stat-icon.green{background:linear-gradient(135deg,var(--neon-green),var(--neon-teal))}
.r-stat-icon.cyan{background:linear-gradient(135deg,var(--neon-cyan),var(--neon-blue))}
.r-stat-val{font-family:var(--font-mono);font-size:1.6rem;font-weight:700;color:#fff;margin-bottom:.3rem}
.r-stat-lbl{font-family:var(--font-mono);font-size:.55rem;color:var(--dark-400);text-transform:uppercase;letter-spacing:.08em}
.done-alert{background:rgba(52,211,153,0.06);border:1px solid rgba(52,211,153,0.2);border-radius:14px;padding:1.8rem;margin-bottom:1.5rem}
.done-alert h4{font-family:var(--font-mono);font-size:.9rem;font-weight:700;color:var(--neon-green);display:flex;align-items:center;gap:.8rem;margin-bottom:.8rem}
.done-alert p{color:var(--dark-200);font-size:.85rem;line-height:1.6;margin-bottom:1.5rem}
.done-actions{display:flex;justify-content:center;gap:.8rem;flex-wrap:wrap}
/* ===== LOG ===== */
.log-head{padding:1.2rem 1.8rem;border-bottom:1px solid rgba(255,255,255,0.05);display:flex;justify-content:space-between;align-items:center}
.log-title{font-family:var(--font-mono);font-size:.78rem;font-weight:600;color:#fff;display:flex;align-items:center;gap:.7rem}
.log-title i{color:var(--neon-purple);font-size:.75rem}
.log-toggle{display:flex;align-items:center;gap:.4rem;padding:.4rem .8rem;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:6px;color:var(--dark-300);font-family:var(--font-mono);font-size:.6rem;cursor:pointer;transition:all .2s}
.log-toggle:hover{background:rgba(255,255,255,0.08);color:#fff}
.log-body{background:rgba(15,23,42,0.6);padding:1.2rem 1.5rem;max-height:260px;overflow-y:auto;border-top:1px solid rgba(255,255,255,0.04)}
.log-entry{font-family:var(--font-mono);font-size:.7rem;line-height:1.5;margin-bottom:.3rem;animation:fadeUp .3s ease}
.log-entry.info{color:var(--neon-cyan)}.log-entry.success{color:var(--neon-green)}.log-entry.warning{color:var(--neon-yellow)}.log-entry.error{color:var(--neon-red)}
.log-entry .ts{color:var(--dark-500);font-weight:600}
/* ===== RESPONSIVE ===== */
@@media(max-width:1200px){.pipe-grid{grid-template-columns:1fr}}
@@media(max-width:768px){
.header-row{flex-direction:column}.header-title{font-size:2rem}
.results-stats{grid-template-columns:repeat(2,1fr)}.ctrl-foot{flex-direction:column;align-items:stretch}
.ctrl-btns{justify-content:center}.done-actions{flex-direction:column}
}
@@media(max-width:480px){.results-stats{grid-template-columns:1fr}.stats-grid{grid-template-columns:1fr}}
@@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
</style>
}
<div class="nex-bg"><div class="grid"></div><div class="mesh"></div></div>
<!-- Header -->
<section class="page-header">
<div class="container">
<div class="breadcrumb-nex">
<a href="@Url.Action("Index")"><i class="fa-solid fa-brain"></i> Analysis Dashboard</a>
<i class="fa-solid fa-chevron-right sep"></i>
<a href="@Url.Action("AnalyzeQuestionnaire", new { id = ViewBag.QuestionnaireId })">@ViewBag.QuestionnaireName</a>
<i class="fa-solid fa-chevron-right sep"></i>
<span class="current">Batch Analysis</span>
</div>
<div class="header-row">
<div class="header-left">
<div class="header-badge"><span class="dot"></span> CLAUDE AI BATCH PROCESSOR</div>
<h1 class="header-title">Batch <span class="grad">Analysis</span></h1>
<p class="header-sub">Processing @ViewBag.TotalRequests mental health survey responses through the Claude AI pipeline</p>
</div>
<div class="header-actions">
<button type="button" class="h-btn sec" onclick="goBack()"><i class="fa-solid fa-arrow-left"></i> Back</button>
</div>
</div>
</div>
</section>
<!-- Processing Card -->
<section style="position:relative;z-index:10;padding:2rem 0">
<div class="container">
<div class="nex-card">
<!-- Header -->
<div class="proc-head">
<div class="proc-head-info">
<h3><i class="fa-solid fa-sparkles"></i> Claude AI Analysis Pipeline</h3>
<p>Sentiment, risk assessment, key phrases, and workplace insights</p>
</div>
<div id="statusBadge" class="status-chip init"><i class="fa-solid fa-clock"></i> INITIALIZING</div>
</div>
<!-- Progress -->
<div class="prog-section">
<div class="prog-top">
<div class="prog-label">Analysis Progress</div>
<div id="progressPercentage" class="prog-pct">0%</div>
</div>
<div class="prog-bar-wrap">
<div class="prog-bar">
<div id="progressBar" class="prog-fill animated" style="width:0%">
<div id="progressText" class="prog-text">Starting…</div>
</div>
</div>
</div>
<div class="prog-info">
<div class="prog-count"><span id="processedCount">0</span> of @ViewBag.TotalRequests responses</div>
<div id="estimatedTime" class="prog-eta">Estimated: Calculating…</div>
</div>
</div>
<!-- Pipeline + Stats -->
<div class="pipe-grid">
<div class="pipe-box">
<div class="pipe-title"><i class="fa-solid fa-list-check"></i> Processing Pipeline</div>
<div id="step1" class="pipe-step"><div class="s-icon"><i class="fa-solid fa-circle"></i></div><span>Connecting to Claude AI…</span></div>
<div id="step2" class="pipe-step"><div class="s-icon"><i class="fa-solid fa-circle"></i></div><span>Anonymizing personal information…</span></div>
<div id="step3" class="pipe-step"><div class="s-icon"><i class="fa-solid fa-circle"></i></div><span>Running sentiment analysis…</span></div>
<div id="step4" class="pipe-step"><div class="s-icon"><i class="fa-solid fa-circle"></i></div><span>Extracting key phrases & themes…</span></div>
<div id="step5" class="pipe-step"><div class="s-icon"><i class="fa-solid fa-circle"></i></div><span>Assessing mental health risk levels…</span></div>
<div id="step6" class="pipe-step"><div class="s-icon"><i class="fa-solid fa-circle"></i></div><span>Generating workplace insights…</span></div>
<div id="step7" class="pipe-step"><div class="s-icon"><i class="fa-solid fa-circle"></i></div><span>Creating executive summary…</span></div>
</div>
<div class="pipe-box">
<div class="pipe-title"><i class="fa-solid fa-chart-column"></i> Real-time Statistics</div>
<div class="stats-grid">
<div class="stat-card pos"><div class="stat-icon"><i class="fa-solid fa-face-smile"></i></div><div id="positiveCount" class="stat-val">0</div><div class="stat-lbl">Positive</div></div>
<div class="stat-card neg"><div class="stat-icon"><i class="fa-solid fa-face-frown"></i></div><div id="negativeCount" class="stat-val">0</div><div class="stat-lbl">Negative</div></div>
<div class="stat-card low"><div class="stat-icon"><i class="fa-solid fa-shield-halved"></i></div><div id="lowRiskCount" class="stat-val">0</div><div class="stat-lbl">Low Risk</div></div>
<div class="stat-card high"><div class="stat-icon"><i class="fa-solid fa-triangle-exclamation"></i></div><div id="highRiskCount" class="stat-val">0</div><div class="stat-lbl">High Risk</div></div>
</div>
</div>
</div>
<!-- Controls -->
<div class="ctrl-foot">
<div id="currentStatus" class="ctrl-status"><i class="fa-solid fa-circle-info"></i> Ready to start batch processing…</div>
<div class="ctrl-btns">
<button id="startButton" type="button" class="btn-go green" onclick="startBatchAnalysis()"><i class="fa-solid fa-play"></i> START</button>
<button id="stopButton" type="button" class="btn-go red d-none" onclick="stopBatchAnalysis()"><i class="fa-solid fa-stop"></i> STOP</button>
</div>
</div>
</div>
<!-- Results (hidden until done) -->
<div id="resultsSection" class="nex-card d-none" style="overflow:hidden">
<div class="results-head">
<div class="r-icon"><i class="fa-solid fa-circle-check"></i></div>
<h3>Batch Analysis Complete</h3>
</div>
<div class="results-body">
<div class="results-stats">
<div class="r-stat"><div class="r-stat-icon blue"><i class="fa-solid fa-check-double"></i></div><div id="totalProcessed" class="r-stat-val">0</div><div class="r-stat-lbl">Processed</div></div>
<div class="r-stat"><div class="r-stat-icon red"><i class="fa-solid fa-triangle-exclamation"></i></div><div id="finalHighRisk" class="r-stat-val">0</div><div class="r-stat-lbl">High Risk</div></div>
<div class="r-stat"><div class="r-stat-icon green"><i class="fa-solid fa-face-smile"></i></div><div id="finalPositive" class="r-stat-val">0</div><div class="r-stat-lbl">Positive</div></div>
<div class="r-stat"><div class="r-stat-icon cyan"><i class="fa-solid fa-clock"></i></div><div id="processingTime" class="r-stat-val">0s</div><div class="r-stat-lbl">Duration</div></div>
</div>
<div class="done-alert">
<h4><i class="fa-solid fa-sparkles"></i> Analysis Successfully Completed</h4>
<p>All responses have been analyzed through the Claude AI pipeline — sentiment patterns, risk levels, and workplace insights are ready for review.</p>
<div class="done-actions">
<a href="@Url.Action("AnalyzeQuestionnaire", new { id = ViewBag.QuestionnaireId })" class="btn-go pri"><i class="fa-solid fa-chart-line"></i> VIEW ANALYSIS</a>
<a id="viewHighRiskButton" href="@Url.Action("HighRiskResponses", new { id = ViewBag.QuestionnaireId })" class="btn-go red d-none"><i class="fa-solid fa-shield-halved"></i> HIGH RISK CASES</a>
<a href="@Url.Action("GenerateReport", new { id = ViewBag.QuestionnaireId })" class="btn-go cyan"><i class="fa-solid fa-file-lines"></i> GENERATE REPORT</a>
</div>
</div>
</div>
</div>
<!-- Log -->
<div class="nex-card">
<div class="log-head">
<div class="log-title"><i class="fa-solid fa-terminal"></i> Processing Log</div>
<button type="button" class="log-toggle" onclick="toggleLog()"><i class="fa-solid fa-chevron-down"></i> SHOW</button>
</div>
<div id="logContainer" class="log-body d-none">
<div id="processingLog">
<div class="log-entry info">[<span class="ts"></span>] Batch analysis system initialized…</div>
</div>
</div>
</div>
</div>
</section>
@section Scripts {
<script>
let batchProcessing = false;
let startTime = null;
let processedCount = 0;
let totalRequests = @ViewBag.TotalRequests;
let questionnaireId = @ViewBag.QuestionnaireId;
function startBatchAnalysis() {
if (batchProcessing) return;
batchProcessing = true;
startTime = new Date();
processedCount = 0;
document.getElementById('startButton').classList.add('d-none');
document.getElementById('stopButton').classList.remove('d-none');
setStatus('processing', 'fa-spinner fa-spin', 'PROCESSING');
animateSteps();
addLog('Starting batch analysis of ' + totalRequests + ' responses…', 'info');
setCtrl('Connecting to Claude AI…');
processBatch();
}
function stopBatchAnalysis() {
batchProcessing = false;
document.getElementById('startButton').classList.remove('d-none');
document.getElementById('stopButton').classList.add('d-none');
setStatus('init', 'fa-pause', 'STOPPED');
setCtrl('Processing stopped by user');
addLog('Batch processing stopped by user', 'warning');
}
function processBatch() {
if (!batchProcessing) return;
addLog('Processing responses through Claude AI pipeline…', 'info');
setCtrl('Analyzing responses…');
$.post('@Url.Action("ProcessBatchAnalysis")', { questionnaireId: questionnaireId })
.done(function(data) {
if (data.success && batchProcessing) {
addLog('Successfully processed ' + data.processedCount + ' responses', 'success');
if (data.highRiskCount > 0)
addLog('ALERT: Found ' + data.highRiskCount + ' high-risk cases', 'warning');
completeBatch(data);
} else {
handleError(data.message || 'Unknown error');
}
})
.fail(function(xhr, status, error) {
handleError('Network error: ' + error);
});
}
function simulateProgress() {
let cur = 0;
const iv = setInterval(() => {
if (!batchProcessing || cur >= 95) { clearInterval(iv); return; }
cur += Math.random() * 12 + 3;
cur = Math.min(cur, 95);
updateProgress(cur);
const sim = Math.floor((cur / 100) * totalRequests);
document.getElementById('processedCount').textContent = sim;
if (cur > 40) {
document.getElementById('positiveCount').textContent = Math.floor(sim * 0.6);
document.getElementById('negativeCount').textContent = Math.floor(sim * 0.3);
document.getElementById('lowRiskCount').textContent = Math.floor(sim * 0.7);
document.getElementById('highRiskCount').textContent = Math.floor(sim * 0.1);
}
const elapsed = (new Date() - startTime) / 1000;
const eta = Math.max(0, (elapsed / cur) * (100 - cur));
document.getElementById('estimatedTime').textContent = 'Estimated: ' + Math.round(eta) + 's remaining';
}, 900 + Math.random() * 1400);
}
function completeBatch(data) {
batchProcessing = false;
updateProgress(100);
document.getElementById('processedCount').textContent = data.processedCount;
document.getElementById('estimatedTime').textContent = 'Complete!';
document.getElementById('totalProcessed').textContent = data.processedCount;
document.getElementById('finalHighRisk').textContent = data.highRiskCount;
const t = Math.round((new Date() - startTime) / 1000);
document.getElementById('processingTime').textContent = t + 's';
setStatus('success', 'fa-check', 'COMPLETE');
completeAllSteps();
document.getElementById('resultsSection').classList.remove('d-none');
if (data.highRiskCount > 0) document.getElementById('viewHighRiskButton').classList.remove('d-none');
document.getElementById('stopButton').classList.add('d-none');
setCtrl('Successfully analyzed ' + data.processedCount + ' responses!');
addLog('Batch completed in ' + t + 's', 'success');
setTimeout(() => {
document.getElementById('resultsSection').scrollIntoView({ behavior: 'smooth' });
}, 800);
}
function handleError(msg) {
batchProcessing = false;
setStatus('error', 'fa-xmark', 'ERROR');
document.getElementById('startButton').classList.remove('d-none');
document.getElementById('stopButton').classList.add('d-none');
setCtrl('Error: ' + msg);
addLog('Error: ' + msg, 'error');
alert('Processing Error: ' + msg);
}
function updateProgress(pct) {
const bar = document.getElementById('progressBar');
const txt = document.getElementById('progressText');
bar.style.width = pct + '%';
document.getElementById('progressPercentage').textContent = Math.round(pct) + '%';
if (pct >= 100) { bar.classList.remove('animated'); bar.classList.add('complete'); txt.textContent = 'Complete!'; }
else { txt.textContent = 'Processing…'; }
}
function setStatus(cls, icon, text) {
const b = document.getElementById('statusBadge');
b.className = 'status-chip ' + cls;
b.innerHTML = '<i class="fa-solid ' + icon + '"></i> ' + text;
}
function setCtrl(msg) {
document.getElementById('currentStatus').innerHTML = '<i class="fa-solid fa-circle-info"></i> ' + msg;
}
const stepIds = ['step1','step2','step3','step4','step5','step6','step7'];
function animateSteps() {
let cur = 0;
const iv = setInterval(() => {
if (!batchProcessing || cur >= stepIds.length) { clearInterval(iv); return; }
if (cur > 0) {
const prev = document.getElementById(stepIds[cur - 1]);
prev.querySelector('.s-icon i').className = 'fa-solid fa-circle-check';
prev.classList.remove('active'); prev.classList.add('done');
}
const step = document.getElementById(stepIds[cur]);
step.querySelector('.s-icon i').className = 'fa-solid fa-circle-notch';
step.classList.add('active');
cur++;
}, 1500 + Math.random() * 2000);
}
function completeAllSteps() {
stepIds.forEach(id => {
const s = document.getElementById(id);
s.querySelector('.s-icon i').className = 'fa-solid fa-circle-check';
s.classList.remove('active'); s.classList.add('done');
});
}
function addLog(msg, type) {
const log = document.getElementById('processingLog');
const ts = new Date().toLocaleTimeString();
const el = document.createElement('div');
el.className = 'log-entry ' + type;
el.innerHTML = '[<span class="ts">' + ts + '</span>] ' + msg;
log.appendChild(el);
const c = document.getElementById('logContainer');
if (!c.classList.contains('d-none')) c.scrollTop = c.scrollHeight;
}
function toggleLog() {
const c = document.getElementById('logContainer');
const btn = document.querySelector('.log-toggle');
c.classList.toggle('d-none');
btn.innerHTML = c.classList.contains('d-none')
? '<i class="fa-solid fa-chevron-down"></i> SHOW'
: '<i class="fa-solid fa-chevron-up"></i> HIDE';
if (!c.classList.contains('d-none')) c.scrollTop = c.scrollHeight;
}
function goBack() {
if (batchProcessing) {
if (confirm('Analysis is running. Stop and go back?')) {
stopBatchAnalysis();
window.location.href = '@Url.Action("AnalyzeQuestionnaire", new { id = ViewBag.QuestionnaireId })';
}
} else {
window.location.href = '@Url.Action("AnalyzeQuestionnaire", new { id = ViewBag.QuestionnaireId })';
}
}
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.ts').forEach(el => { el.textContent = new Date().toLocaleTimeString(); });
if (totalRequests > 0) {
setTimeout(() => {
addLog('Auto-starting batch analysis…', 'info');
startBatchAnalysis();
simulateProgress();
}, 800);
}
});
window.addEventListener('beforeunload', function (e) {
if (batchProcessing) { e.preventDefault(); e.returnValue = 'Analysis is in progress.'; }
});
</script>
}