kiku-agent 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +4 -12
- package/server/agent/eval/AGENTS.md +228 -0
- package/server/agent/eval/analyze.py +684 -0
- package/server/agent/eval/gates.json +15 -0
- package/server/agent/eval/modules/analysis/compare.js +198 -0
- package/server/agent/eval/modules/analysis/gates.js +72 -0
- package/server/agent/eval/modules/analysis/judge.js +70 -0
- package/server/agent/eval/modules/core/cli.js +453 -0
- package/server/agent/eval/modules/core/config.js +60 -0
- package/server/agent/eval/modules/core/store.js +236 -0
- package/server/agent/eval/modules/evaluation/cases.js +689 -0
- package/server/agent/eval/modules/evaluation/runner.js +298 -0
- package/server/agent/eval/modules/evaluation/scoring.js +351 -0
- package/server/agent/eval/modules/evaluation/worker.js +122 -0
- package/server/agent/eval/modules/reporting/benchmark.js +265 -0
- package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
- package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
- package/server/agent/eval/modules/server/evalApi.js +1099 -0
- package/server/agent/eval/modules/ui/evalUi.js +4796 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -0
- package/server/agent/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +127 -0
- package/server/agent/tests/apiRouter.test.js +108 -0
- package/server/agent/tests/architectureFixes.test.js +81 -0
- package/server/agent/tests/architectureRefactoring.test.js +194 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +251 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/citations.test.js +180 -0
- package/server/agent/tests/clarificationAnalytics.test.js +219 -0
- package/server/agent/tests/clarifyingQuestions.test.js +594 -0
- package/server/agent/tests/cliAutomation.test.js +121 -0
- package/server/agent/tests/cloudProviders.test.js +234 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +417 -0
- package/server/agent/tests/contextIntelligence.test.js +162 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +135 -0
- package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
- package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
- package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +49 -0
- package/server/agent/tests/entertainmentMetrics.test.js +126 -0
- package/server/agent/tests/envBootstrap.test.js +87 -0
- package/server/agent/tests/envCommand.test.js +215 -0
- package/server/agent/tests/errorHandling.test.js +80 -0
- package/server/agent/tests/errors.test.js +13 -0
- package/server/agent/tests/evalAnalyze.test.js +54 -0
- package/server/agent/tests/evalCommand.test.js +54 -0
- package/server/agent/tests/evalCorpus.test.js +126 -0
- package/server/agent/tests/evalHarness.test.js +62 -0
- package/server/agent/tests/evalJudgeContext.test.js +38 -0
- package/server/agent/tests/evalMetrics.test.js +312 -0
- package/server/agent/tests/evalStore.test.js +190 -0
- package/server/agent/tests/evalStudio.test.js +179 -0
- package/server/agent/tests/feedbackLearning.test.js +186 -0
- package/server/agent/tests/firstRun.test.js +44 -0
- package/server/agent/tests/gemma4eOptimization.test.js +95 -0
- package/server/agent/tests/hardwareTiers.test.js +206 -0
- package/server/agent/tests/hybridWebSearch.test.js +192 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +319 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/modelManager.test.js +109 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/newCommands.test.js +347 -0
- package/server/agent/tests/newCommandsTui.test.js +141 -0
- package/server/agent/tests/newTools.test.js +113 -0
- package/server/agent/tests/newsIntelligence.test.js +178 -0
- package/server/agent/tests/nextGenArchitecture.test.js +99 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/providerPersistence.test.js +39 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/realWorldValidation.test.js +391 -0
- package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
- package/server/agent/tests/runtimeWatchdog.test.js +32 -0
- package/server/agent/tests/sanitizer.test.js +37 -0
- package/server/agent/tests/scenariosData.js +378 -0
- package/server/agent/tests/schemaRouting.test.js +54 -0
- package/server/agent/tests/securityAuditRemediation.test.js +214 -0
- package/server/agent/tests/securityHardening.test.js +146 -0
- package/server/agent/tests/securitySanitizer.test.js +38 -0
- package/server/agent/tests/sessionAndCopy.test.js +232 -0
- package/server/agent/tests/sidebarControls.test.js +101 -0
- package/server/agent/tests/statefulMemory.test.js +270 -0
- package/server/agent/tests/subagent.test.js +216 -0
- package/server/agent/tests/subagentProgress.test.js +109 -0
- package/server/agent/tests/subagentTextFallback.test.js +115 -0
- package/server/agent/tests/suggestions.test.js +199 -0
- package/server/agent/tests/suggestionsTui.test.js +251 -0
- package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
- package/server/agent/tests/terminalImage.test.js +80 -0
- package/server/agent/tests/terminalMarkdown.test.js +166 -0
- package/server/agent/tests/terminalText.test.js +133 -0
- package/server/agent/tests/textProtocolLoop.test.js +126 -0
- package/server/agent/tests/textToolParsing.test.js +139 -0
- package/server/agent/tests/theme.test.js +26 -0
- package/server/agent/tests/toolRegistryParity.test.js +53 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +119 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +152 -0
- package/server/agent/tests/v110Features.test.js +262 -0
- package/server/agent/tests/versionBump.test.js +69 -0
- package/server/agent/tests/vramEstimator.test.js +520 -0
- package/server/agent/tests/webChatInspection.test.js +300 -0
- package/server/agent/tests/webChatMemory.test.js +112 -0
- package/server/agent/tests/webChatSecurity.test.js +169 -0
- package/server/agent/tests/webExtract.test.js +261 -0
- package/server/agent/tests/webSources.test.js +247 -0
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/tests/connection.test.js +156 -0
- package/server/supabase_schema.sql +63 -0
|
@@ -0,0 +1,4796 @@
|
|
|
1
|
+
import { cases, expandedCases, securityCases, allCases, legacyCases } from '../evaluation/cases.js';
|
|
2
|
+
|
|
3
|
+
export function getEvalUiHtml() {
|
|
4
|
+
const catalogCount = cases.length;
|
|
5
|
+
const expandedCount = expandedCases ? expandedCases.length : 0;
|
|
6
|
+
const securityCount = securityCases.length;
|
|
7
|
+
const legacyCount = legacyCases.length;
|
|
8
|
+
const totalCount = allCases.length;
|
|
9
|
+
return `<!DOCTYPE html>
|
|
10
|
+
<html lang="en" data-theme="dark">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
14
|
+
<title>Kiku Eval Studio — AI Agent Evaluation Platform</title>
|
|
15
|
+
<link rel="icon" href="/favicon.ico">
|
|
16
|
+
<link rel="stylesheet" href="/fonts/google-sans-official.css">
|
|
17
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
18
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
19
|
+
<link href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600;700;800;900&family=Google+Sans+Text:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
20
|
+
<style>
|
|
21
|
+
:root {
|
|
22
|
+
--font-main: 'Google Sans', 'Google Sans Text', 'Product Sans', 'Noto Sans Thai', 'Noto Sans SC', system-ui, -apple-system, sans-serif;
|
|
23
|
+
--font-mono: 'JetBrains Mono', 'Google Sans Code', ui-monospace, monospace;
|
|
24
|
+
--bg-base: #07090e;
|
|
25
|
+
--bg-surface: #0e121a;
|
|
26
|
+
--bg-card: rgba(16, 20, 30, 0.72);
|
|
27
|
+
--bg-glass: rgba(22, 28, 42, 0.55);
|
|
28
|
+
--border-glass: rgba(255, 255, 255, 0.08);
|
|
29
|
+
--border-focus: rgba(244, 114, 182, 0.4);
|
|
30
|
+
--accent-rose: #f472b6;
|
|
31
|
+
--accent-rose-glow: rgba(244, 114, 182, 0.25);
|
|
32
|
+
--accent-violet: #a78bfa;
|
|
33
|
+
--accent-cyan: #38bdf8;
|
|
34
|
+
--accent-emerald: #34d399;
|
|
35
|
+
--accent-amber: #fbbf24;
|
|
36
|
+
--accent-red: #f87171;
|
|
37
|
+
--accent-rose-dim: rgba(244, 114, 182, 0.12);
|
|
38
|
+
--accent-emerald-dim: rgba(52, 211, 153, 0.12);
|
|
39
|
+
--accent-amber-dim: rgba(251, 191, 36, 0.12);
|
|
40
|
+
--accent-red-dim: rgba(248, 113, 113, 0.12);
|
|
41
|
+
--text-main: #f8fafc;
|
|
42
|
+
--text-muted: #94a3b8;
|
|
43
|
+
--text-dim: #64748b;
|
|
44
|
+
--radius-sm: 8px;
|
|
45
|
+
--radius-md: 14px;
|
|
46
|
+
--radius-lg: 22px;
|
|
47
|
+
}
|
|
48
|
+
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; font-family: var(--font-main); }
|
|
49
|
+
html {
|
|
50
|
+
scroll-behavior: smooth;
|
|
51
|
+
-webkit-text-size-adjust: 100%;
|
|
52
|
+
max-width: 100vw;
|
|
53
|
+
overflow-x: hidden;
|
|
54
|
+
}
|
|
55
|
+
body {
|
|
56
|
+
font-family: var(--font-main);
|
|
57
|
+
background-color: var(--bg-base);
|
|
58
|
+
color: var(--text-main);
|
|
59
|
+
min-height: 100vh;
|
|
60
|
+
min-height: 100dvh;
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
background-image:
|
|
64
|
+
radial-gradient(circle at 15% 10%, rgba(244, 114, 182, 0.09) 0%, transparent 45%),
|
|
65
|
+
radial-gradient(circle at 85% 20%, rgba(167, 139, 250, 0.09) 0%, transparent 45%),
|
|
66
|
+
radial-gradient(circle at 50% 90%, rgba(56, 189, 248, 0.06) 0%, transparent 55%);
|
|
67
|
+
background-attachment: fixed;
|
|
68
|
+
max-width: 100vw;
|
|
69
|
+
overflow-x: hidden;
|
|
70
|
+
-webkit-overflow-scrolling: touch;
|
|
71
|
+
}
|
|
72
|
+
::-webkit-scrollbar {
|
|
73
|
+
width: 6px;
|
|
74
|
+
height: 6px;
|
|
75
|
+
}
|
|
76
|
+
::-webkit-scrollbar-track {
|
|
77
|
+
background: rgba(0, 0, 0, 0.2);
|
|
78
|
+
}
|
|
79
|
+
::-webkit-scrollbar-thumb {
|
|
80
|
+
background: rgba(244, 114, 182, 0.35);
|
|
81
|
+
border-radius: 999px;
|
|
82
|
+
}
|
|
83
|
+
::-webkit-scrollbar-thumb:hover {
|
|
84
|
+
background: var(--accent-rose);
|
|
85
|
+
}
|
|
86
|
+
* {
|
|
87
|
+
scrollbar-width: thin;
|
|
88
|
+
scrollbar-color: rgba(244, 114, 182, 0.35) rgba(0, 0, 0, 0.2);
|
|
89
|
+
}
|
|
90
|
+
header {
|
|
91
|
+
backdrop-filter: blur(24px);
|
|
92
|
+
-webkit-backdrop-filter: blur(24px);
|
|
93
|
+
background: rgba(11, 14, 22, 0.9);
|
|
94
|
+
border-bottom: 1px solid var(--border-glass);
|
|
95
|
+
position: sticky;
|
|
96
|
+
top: 0;
|
|
97
|
+
z-index: 40;
|
|
98
|
+
padding: max(10px, env(safe-area-inset-top)) 14px 10px 14px;
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: space-between;
|
|
102
|
+
gap: 10px;
|
|
103
|
+
width: 100%;
|
|
104
|
+
max-width: 100vw;
|
|
105
|
+
box-sizing: border-box;
|
|
106
|
+
}
|
|
107
|
+
@media (min-width: 641px) {
|
|
108
|
+
header { padding: 14px 24px; gap: 16px; }
|
|
109
|
+
.header-actions { gap: 12px; }
|
|
110
|
+
}
|
|
111
|
+
@media (max-width: 640px) {
|
|
112
|
+
header {
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
align-items: stretch;
|
|
115
|
+
gap: 10px;
|
|
116
|
+
padding: max(10px, env(safe-area-inset-top)) 14px 12px 14px;
|
|
117
|
+
}
|
|
118
|
+
.brand {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: 10px;
|
|
122
|
+
width: 100%;
|
|
123
|
+
}
|
|
124
|
+
.brand-title { font-size: 16px; }
|
|
125
|
+
.brand-icon { font-size: 22px; }
|
|
126
|
+
.brand-tag { display: inline-block; }
|
|
127
|
+
.header-actions {
|
|
128
|
+
display: grid;
|
|
129
|
+
grid-template-columns: 1.1fr 1fr 1fr;
|
|
130
|
+
align-items: center;
|
|
131
|
+
width: 100%;
|
|
132
|
+
gap: 8px;
|
|
133
|
+
}
|
|
134
|
+
.status-pill {
|
|
135
|
+
width: 100%;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
min-height: 38px;
|
|
138
|
+
padding: 6px 8px;
|
|
139
|
+
font-size: 11px;
|
|
140
|
+
}
|
|
141
|
+
.btn {
|
|
142
|
+
width: 100%;
|
|
143
|
+
min-height: 38px;
|
|
144
|
+
font-size: 12.5px;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
}
|
|
147
|
+
.form-control, .search-input { font-size: 16px; }
|
|
148
|
+
}
|
|
149
|
+
.brand {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 10px;
|
|
153
|
+
text-decoration: none;
|
|
154
|
+
color: inherit;
|
|
155
|
+
flex-shrink: 0;
|
|
156
|
+
}
|
|
157
|
+
.brand-icon {
|
|
158
|
+
font-size: 24px;
|
|
159
|
+
line-height: 1;
|
|
160
|
+
filter: drop-shadow(0 0 10px rgba(244, 114, 182, 0.4));
|
|
161
|
+
}
|
|
162
|
+
.brand-title {
|
|
163
|
+
font-family: var(--font-main);
|
|
164
|
+
font-size: 18px;
|
|
165
|
+
font-weight: 700;
|
|
166
|
+
background: linear-gradient(135deg, #fff 40%, var(--accent-rose) 100%);
|
|
167
|
+
-webkit-background-clip: text;
|
|
168
|
+
-webkit-text-fill-color: transparent;
|
|
169
|
+
letter-spacing: -0.02em;
|
|
170
|
+
}
|
|
171
|
+
.brand-tag {
|
|
172
|
+
display: none;
|
|
173
|
+
font-size: 10px;
|
|
174
|
+
font-weight: 600;
|
|
175
|
+
padding: 2px 7px;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
background: var(--accent-rose-dim);
|
|
178
|
+
color: var(--accent-rose);
|
|
179
|
+
border: 1px solid rgba(244, 114, 182, 0.3);
|
|
180
|
+
text-transform: uppercase;
|
|
181
|
+
letter-spacing: 0.05em;
|
|
182
|
+
}
|
|
183
|
+
@media (min-width: 768px) {
|
|
184
|
+
.brand-title { font-size: 20px; }
|
|
185
|
+
.brand-tag { display: inline-block; }
|
|
186
|
+
}
|
|
187
|
+
.header-actions {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
gap: 8px;
|
|
191
|
+
}
|
|
192
|
+
.status-pill {
|
|
193
|
+
display: inline-flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: 6px;
|
|
196
|
+
padding: 5px 10px;
|
|
197
|
+
border-radius: 999px;
|
|
198
|
+
font-size: 11px;
|
|
199
|
+
font-weight: 500;
|
|
200
|
+
background: var(--bg-glass);
|
|
201
|
+
border: 1px solid var(--border-glass);
|
|
202
|
+
color: var(--text-muted);
|
|
203
|
+
white-space: nowrap;
|
|
204
|
+
}
|
|
205
|
+
.status-dot {
|
|
206
|
+
width: 7px;
|
|
207
|
+
height: 7px;
|
|
208
|
+
border-radius: 50%;
|
|
209
|
+
background: var(--text-dim);
|
|
210
|
+
flex-shrink: 0;
|
|
211
|
+
}
|
|
212
|
+
.status-dot.active {
|
|
213
|
+
background: var(--accent-emerald);
|
|
214
|
+
box-shadow: 0 0 8px var(--accent-emerald);
|
|
215
|
+
animation: pulse 1.8s infinite;
|
|
216
|
+
}
|
|
217
|
+
.status-dot.running {
|
|
218
|
+
background: var(--accent-cyan);
|
|
219
|
+
box-shadow: 0 0 8px var(--accent-cyan);
|
|
220
|
+
animation: pulse 0.9s infinite;
|
|
221
|
+
}
|
|
222
|
+
@keyframes pulse {
|
|
223
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
224
|
+
50% { opacity: 0.35; transform: scale(1.3); }
|
|
225
|
+
}
|
|
226
|
+
.btn {
|
|
227
|
+
display: inline-flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
230
|
+
gap: 6px;
|
|
231
|
+
padding: 8px 14px;
|
|
232
|
+
font-size: 12px;
|
|
233
|
+
font-weight: 600;
|
|
234
|
+
border-radius: var(--radius-sm);
|
|
235
|
+
border: 1px solid transparent;
|
|
236
|
+
cursor: pointer;
|
|
237
|
+
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
238
|
+
font-family: inherit;
|
|
239
|
+
min-height: 38px;
|
|
240
|
+
touch-action: manipulation;
|
|
241
|
+
}
|
|
242
|
+
@media (min-width: 640px) {
|
|
243
|
+
.btn { padding: 9px 18px; font-size: 13px; min-height: 40px; }
|
|
244
|
+
}
|
|
245
|
+
.btn-primary {
|
|
246
|
+
background: linear-gradient(135deg, #ec4899 0%, #8b5cf6 100%);
|
|
247
|
+
color: #fff;
|
|
248
|
+
box-shadow: 0 4px 14px rgba(236, 72, 153, 0.3);
|
|
249
|
+
}
|
|
250
|
+
.btn-primary:hover:not(:disabled) {
|
|
251
|
+
box-shadow: 0 6px 20px rgba(236, 72, 153, 0.45);
|
|
252
|
+
transform: translateY(-1px);
|
|
253
|
+
}
|
|
254
|
+
.btn-danger {
|
|
255
|
+
background: var(--accent-red-dim);
|
|
256
|
+
color: var(--accent-red);
|
|
257
|
+
border-color: rgba(248, 113, 113, 0.3);
|
|
258
|
+
}
|
|
259
|
+
.btn-danger:hover:not(:disabled) {
|
|
260
|
+
background: rgba(248, 113, 113, 0.25);
|
|
261
|
+
}
|
|
262
|
+
.btn-secondary {
|
|
263
|
+
background: var(--bg-glass);
|
|
264
|
+
color: var(--text-main);
|
|
265
|
+
border-color: var(--border-glass);
|
|
266
|
+
}
|
|
267
|
+
.btn-secondary:hover:not(:disabled) {
|
|
268
|
+
background: rgba(255, 255, 255, 0.09);
|
|
269
|
+
}
|
|
270
|
+
.btn-warning {
|
|
271
|
+
background: var(--accent-amber-dim);
|
|
272
|
+
color: var(--accent-amber);
|
|
273
|
+
border-color: rgba(251, 191, 36, 0.35);
|
|
274
|
+
}
|
|
275
|
+
.btn-warning:hover:not(:disabled) {
|
|
276
|
+
background: rgba(251, 191, 36, 0.25);
|
|
277
|
+
box-shadow: 0 4px 14px rgba(251, 191, 36, 0.25);
|
|
278
|
+
}
|
|
279
|
+
.btn-run-case {
|
|
280
|
+
padding: 4px 10px;
|
|
281
|
+
font-size: 11px;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
border-radius: 4px;
|
|
284
|
+
background: rgba(255, 255, 255, 0.08);
|
|
285
|
+
border: 1px solid var(--border-glass);
|
|
286
|
+
color: var(--text-main);
|
|
287
|
+
cursor: pointer;
|
|
288
|
+
transition: all 0.15s ease;
|
|
289
|
+
}
|
|
290
|
+
.btn-run-case:hover {
|
|
291
|
+
background: rgba(255, 255, 255, 0.16);
|
|
292
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
293
|
+
}
|
|
294
|
+
.btn-run-case.btn-warning {
|
|
295
|
+
background: var(--accent-amber-dim);
|
|
296
|
+
color: var(--accent-amber);
|
|
297
|
+
border-color: rgba(251, 191, 36, 0.4);
|
|
298
|
+
}
|
|
299
|
+
.btn-run-case.btn-warning:hover {
|
|
300
|
+
background: rgba(251, 191, 36, 0.28);
|
|
301
|
+
}
|
|
302
|
+
.pill-anomaly {
|
|
303
|
+
background: var(--accent-amber-dim);
|
|
304
|
+
color: var(--accent-amber);
|
|
305
|
+
border: 1px solid rgba(251, 191, 36, 0.35);
|
|
306
|
+
}
|
|
307
|
+
.btn:disabled {
|
|
308
|
+
opacity: 0.45;
|
|
309
|
+
cursor: not-allowed;
|
|
310
|
+
transform: none !important;
|
|
311
|
+
box-shadow: none !important;
|
|
312
|
+
}
|
|
313
|
+
main {
|
|
314
|
+
flex: 1;
|
|
315
|
+
max-width: 1440px;
|
|
316
|
+
width: 100%;
|
|
317
|
+
margin: 0 auto;
|
|
318
|
+
padding: 12px 12px max(32px, env(safe-area-inset-bottom));
|
|
319
|
+
display: flex;
|
|
320
|
+
flex-direction: column;
|
|
321
|
+
gap: 14px;
|
|
322
|
+
min-width: 0;
|
|
323
|
+
box-sizing: border-box;
|
|
324
|
+
overflow-x: hidden;
|
|
325
|
+
}
|
|
326
|
+
main > * {
|
|
327
|
+
min-width: 0;
|
|
328
|
+
max-width: 100%;
|
|
329
|
+
box-sizing: border-box;
|
|
330
|
+
}
|
|
331
|
+
@media (min-width: 640px) {
|
|
332
|
+
main { padding: 20px 20px max(32px, env(safe-area-inset-bottom)); gap: 20px; }
|
|
333
|
+
}
|
|
334
|
+
@media (min-width: 1024px) {
|
|
335
|
+
main { padding: 24px; gap: 24px; }
|
|
336
|
+
}
|
|
337
|
+
.preset-chips {
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 8px;
|
|
341
|
+
overflow-x: auto;
|
|
342
|
+
padding: 4px 2px 6px 2px;
|
|
343
|
+
scrollbar-width: none;
|
|
344
|
+
-webkit-overflow-scrolling: touch;
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
width: 100%;
|
|
347
|
+
max-width: 100%;
|
|
348
|
+
min-width: 0;
|
|
349
|
+
box-sizing: border-box;
|
|
350
|
+
}
|
|
351
|
+
.preset-chips::-webkit-scrollbar { display: none; }
|
|
352
|
+
.preset-chip {
|
|
353
|
+
padding: 7px 14px;
|
|
354
|
+
border-radius: 999px;
|
|
355
|
+
font-size: 11.5px;
|
|
356
|
+
font-weight: 500;
|
|
357
|
+
background: var(--bg-card);
|
|
358
|
+
border: 1px solid var(--border-glass);
|
|
359
|
+
color: var(--text-muted);
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
white-space: nowrap;
|
|
362
|
+
transition: all 0.15s;
|
|
363
|
+
min-height: 34px;
|
|
364
|
+
display: inline-flex;
|
|
365
|
+
align-items: center;
|
|
366
|
+
flex-shrink: 0;
|
|
367
|
+
touch-action: manipulation;
|
|
368
|
+
}
|
|
369
|
+
.preset-chip:hover { color: var(--text-main); border-color: rgba(255, 255, 255, 0.18); }
|
|
370
|
+
.preset-chip.active {
|
|
371
|
+
background: var(--accent-rose-dim);
|
|
372
|
+
color: var(--accent-rose);
|
|
373
|
+
border-color: rgba(244, 114, 182, 0.35);
|
|
374
|
+
font-weight: 600;
|
|
375
|
+
}
|
|
376
|
+
.config-card {
|
|
377
|
+
background: var(--bg-card);
|
|
378
|
+
backdrop-filter: blur(20px);
|
|
379
|
+
-webkit-backdrop-filter: blur(20px);
|
|
380
|
+
border: 1px solid var(--border-glass);
|
|
381
|
+
border-radius: var(--radius-md);
|
|
382
|
+
overflow: hidden;
|
|
383
|
+
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.05);
|
|
384
|
+
width: 100%;
|
|
385
|
+
max-width: 100%;
|
|
386
|
+
min-width: 0;
|
|
387
|
+
box-sizing: border-box;
|
|
388
|
+
}
|
|
389
|
+
.config-header {
|
|
390
|
+
padding: 14px 18px;
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: center;
|
|
393
|
+
justify-content: space-between;
|
|
394
|
+
cursor: pointer;
|
|
395
|
+
user-select: none;
|
|
396
|
+
width: 100%;
|
|
397
|
+
box-sizing: border-box;
|
|
398
|
+
}
|
|
399
|
+
.config-title {
|
|
400
|
+
font-family: var(--font-main);
|
|
401
|
+
font-size: 14px;
|
|
402
|
+
font-weight: 600;
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
gap: 8px;
|
|
406
|
+
min-width: 0;
|
|
407
|
+
}
|
|
408
|
+
.config-chevron {
|
|
409
|
+
transition: transform 0.25s ease;
|
|
410
|
+
flex-shrink: 0;
|
|
411
|
+
}
|
|
412
|
+
.config-chevron.open {
|
|
413
|
+
transform: rotate(180deg);
|
|
414
|
+
}
|
|
415
|
+
.config-body {
|
|
416
|
+
padding: 0 18px 18px;
|
|
417
|
+
display: grid;
|
|
418
|
+
grid-template-columns: minmax(0, 1fr);
|
|
419
|
+
gap: 14px;
|
|
420
|
+
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
|
421
|
+
padding-top: 16px;
|
|
422
|
+
width: 100%;
|
|
423
|
+
max-width: 100%;
|
|
424
|
+
min-width: 0;
|
|
425
|
+
box-sizing: border-box;
|
|
426
|
+
}
|
|
427
|
+
@media (min-width: 640px) {
|
|
428
|
+
.config-body { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
429
|
+
}
|
|
430
|
+
@media (min-width: 1024px) {
|
|
431
|
+
.config-body { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
|
432
|
+
}
|
|
433
|
+
#targetCaseGroup {
|
|
434
|
+
grid-column: auto;
|
|
435
|
+
width: 100%;
|
|
436
|
+
min-width: 0;
|
|
437
|
+
}
|
|
438
|
+
@media (min-width: 640px) {
|
|
439
|
+
#targetCaseGroup { grid-column: span 2; }
|
|
440
|
+
}
|
|
441
|
+
@media (min-width: 1024px) {
|
|
442
|
+
#targetCaseGroup { grid-column: span 2; }
|
|
443
|
+
}
|
|
444
|
+
.form-group {
|
|
445
|
+
display: flex;
|
|
446
|
+
flex-direction: column;
|
|
447
|
+
gap: 5px;
|
|
448
|
+
min-width: 0;
|
|
449
|
+
width: 100%;
|
|
450
|
+
max-width: 100%;
|
|
451
|
+
box-sizing: border-box;
|
|
452
|
+
}
|
|
453
|
+
.form-label {
|
|
454
|
+
font-size: 11px;
|
|
455
|
+
font-weight: 600;
|
|
456
|
+
color: var(--text-muted);
|
|
457
|
+
text-transform: uppercase;
|
|
458
|
+
letter-spacing: 0.04em;
|
|
459
|
+
white-space: nowrap;
|
|
460
|
+
overflow: hidden;
|
|
461
|
+
text-overflow: ellipsis;
|
|
462
|
+
}
|
|
463
|
+
.form-control {
|
|
464
|
+
background: rgba(8, 10, 15, 0.75);
|
|
465
|
+
border: 1px solid var(--border-glass);
|
|
466
|
+
color: var(--text-main);
|
|
467
|
+
padding: 9px 12px;
|
|
468
|
+
border-radius: var(--radius-sm);
|
|
469
|
+
font-size: 13px;
|
|
470
|
+
font-family: inherit;
|
|
471
|
+
outline: none;
|
|
472
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
473
|
+
min-height: 40px;
|
|
474
|
+
width: 100%;
|
|
475
|
+
max-width: 100%;
|
|
476
|
+
min-width: 0;
|
|
477
|
+
box-sizing: border-box;
|
|
478
|
+
text-overflow: ellipsis;
|
|
479
|
+
}
|
|
480
|
+
.form-control:focus {
|
|
481
|
+
border-color: var(--accent-rose);
|
|
482
|
+
box-shadow: 0 0 0 2px rgba(244, 114, 182, 0.15);
|
|
483
|
+
}
|
|
484
|
+
select.form-control {
|
|
485
|
+
text-overflow: ellipsis;
|
|
486
|
+
overflow: hidden;
|
|
487
|
+
white-space: nowrap;
|
|
488
|
+
}
|
|
489
|
+
select.form-control option {
|
|
490
|
+
max-width: 100%;
|
|
491
|
+
overflow: hidden;
|
|
492
|
+
text-overflow: ellipsis;
|
|
493
|
+
background: #0e121a;
|
|
494
|
+
color: #f8fafc;
|
|
495
|
+
}
|
|
496
|
+
.progress-hero {
|
|
497
|
+
background: linear-gradient(145deg, rgba(24, 30, 46, 0.65) 0%, rgba(12, 15, 24, 0.95) 100%);
|
|
498
|
+
border: 1px solid var(--border-glass);
|
|
499
|
+
border-radius: var(--radius-md);
|
|
500
|
+
padding: 16px;
|
|
501
|
+
display: flex;
|
|
502
|
+
flex-direction: column;
|
|
503
|
+
gap: 14px;
|
|
504
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
|
|
505
|
+
}
|
|
506
|
+
@media (min-width: 640px) {
|
|
507
|
+
.progress-hero { padding: 22px; gap: 18px; }
|
|
508
|
+
}
|
|
509
|
+
.progress-top {
|
|
510
|
+
display: flex;
|
|
511
|
+
align-items: center;
|
|
512
|
+
justify-content: space-between;
|
|
513
|
+
flex-wrap: wrap;
|
|
514
|
+
gap: 10px;
|
|
515
|
+
}
|
|
516
|
+
.progress-hero-title {
|
|
517
|
+
display: flex;
|
|
518
|
+
align-items: center;
|
|
519
|
+
gap: 8px;
|
|
520
|
+
}
|
|
521
|
+
.progress-title-text {
|
|
522
|
+
font-family: var(--font-main);
|
|
523
|
+
font-size: 16px;
|
|
524
|
+
font-weight: 600;
|
|
525
|
+
}
|
|
526
|
+
@media (min-width: 640px) {
|
|
527
|
+
.progress-title-text { font-size: 18px; }
|
|
528
|
+
}
|
|
529
|
+
.progress-stats-text {
|
|
530
|
+
font-family: 'JetBrains Mono', monospace;
|
|
531
|
+
font-size: 12px;
|
|
532
|
+
color: var(--text-muted);
|
|
533
|
+
}
|
|
534
|
+
.progress-track {
|
|
535
|
+
width: 100%;
|
|
536
|
+
height: 10px;
|
|
537
|
+
background: rgba(0, 0, 0, 0.5);
|
|
538
|
+
border-radius: 999px;
|
|
539
|
+
overflow: hidden;
|
|
540
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
541
|
+
position: relative;
|
|
542
|
+
}
|
|
543
|
+
@media (min-width: 640px) {
|
|
544
|
+
.progress-track { height: 12px; }
|
|
545
|
+
}
|
|
546
|
+
.progress-fill {
|
|
547
|
+
height: 100%;
|
|
548
|
+
width: 0%;
|
|
549
|
+
background: linear-gradient(90deg, #ec4899, #8b5cf6, #38bdf8);
|
|
550
|
+
border-radius: 999px;
|
|
551
|
+
transition: width 0.3s ease;
|
|
552
|
+
box-shadow: 0 0 16px rgba(236, 72, 153, 0.5);
|
|
553
|
+
}
|
|
554
|
+
.active-case-card {
|
|
555
|
+
background: rgba(6, 8, 14, 0.45);
|
|
556
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
557
|
+
border-radius: var(--radius-sm);
|
|
558
|
+
padding: 12px 14px;
|
|
559
|
+
display: flex;
|
|
560
|
+
flex-direction: column;
|
|
561
|
+
gap: 8px;
|
|
562
|
+
}
|
|
563
|
+
@media (min-width: 640px) {
|
|
564
|
+
.active-case-card {
|
|
565
|
+
flex-direction: row;
|
|
566
|
+
align-items: center;
|
|
567
|
+
justify-content: space-between;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
.active-case-meta {
|
|
571
|
+
display: flex;
|
|
572
|
+
align-items: center;
|
|
573
|
+
gap: 8px;
|
|
574
|
+
overflow: hidden;
|
|
575
|
+
}
|
|
576
|
+
.case-badge-pill {
|
|
577
|
+
font-family: 'JetBrains Mono', monospace;
|
|
578
|
+
font-size: 11px;
|
|
579
|
+
font-weight: 600;
|
|
580
|
+
padding: 2px 7px;
|
|
581
|
+
border-radius: 4px;
|
|
582
|
+
background: var(--accent-cyan);
|
|
583
|
+
color: #06080e;
|
|
584
|
+
flex-shrink: 0;
|
|
585
|
+
}
|
|
586
|
+
.case-question-preview {
|
|
587
|
+
font-size: 13px;
|
|
588
|
+
color: var(--text-main);
|
|
589
|
+
overflow: hidden;
|
|
590
|
+
text-overflow: ellipsis;
|
|
591
|
+
white-space: nowrap;
|
|
592
|
+
}
|
|
593
|
+
@media (max-width: 640px) {
|
|
594
|
+
.case-question-preview {
|
|
595
|
+
white-space: normal;
|
|
596
|
+
display: -webkit-box;
|
|
597
|
+
-webkit-line-clamp: 2;
|
|
598
|
+
-webkit-box-orient: vertical;
|
|
599
|
+
font-size: 12.5px;
|
|
600
|
+
line-height: 1.35;
|
|
601
|
+
}
|
|
602
|
+
.active-case-card {
|
|
603
|
+
padding: 10px 12px;
|
|
604
|
+
gap: 8px;
|
|
605
|
+
border-radius: 10px;
|
|
606
|
+
}
|
|
607
|
+
.active-case-meta {
|
|
608
|
+
align-items: flex-start;
|
|
609
|
+
}
|
|
610
|
+
.progress-hero {
|
|
611
|
+
padding: 14px 12px;
|
|
612
|
+
gap: 12px;
|
|
613
|
+
}
|
|
614
|
+
.gate-banner {
|
|
615
|
+
padding: 12px 14px;
|
|
616
|
+
gap: 6px;
|
|
617
|
+
}
|
|
618
|
+
.gate-title-group {
|
|
619
|
+
display: flex;
|
|
620
|
+
align-items: center;
|
|
621
|
+
justify-content: space-between;
|
|
622
|
+
width: 100%;
|
|
623
|
+
font-size: 12px;
|
|
624
|
+
}
|
|
625
|
+
.config-header {
|
|
626
|
+
padding: 12px 14px;
|
|
627
|
+
}
|
|
628
|
+
.config-body {
|
|
629
|
+
padding: 0 14px 14px;
|
|
630
|
+
gap: 12px;
|
|
631
|
+
}
|
|
632
|
+
#targetCaseGroup {
|
|
633
|
+
grid-column: 1 / -1 !important;
|
|
634
|
+
}
|
|
635
|
+
.explorer-toolbar {
|
|
636
|
+
padding: 10px 12px;
|
|
637
|
+
gap: 10px;
|
|
638
|
+
}
|
|
639
|
+
.mobile-cards-container {
|
|
640
|
+
padding: 10px 8px;
|
|
641
|
+
gap: 9px;
|
|
642
|
+
}
|
|
643
|
+
.detail-section {
|
|
644
|
+
padding: 12px;
|
|
645
|
+
gap: 8px;
|
|
646
|
+
border-radius: 10px;
|
|
647
|
+
}
|
|
648
|
+
pre.code-block {
|
|
649
|
+
font-size: 10.5px;
|
|
650
|
+
padding: 8px 10px;
|
|
651
|
+
border-radius: 8px;
|
|
652
|
+
max-height: 180px;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
.active-tool-live {
|
|
656
|
+
font-family: 'JetBrains Mono', monospace;
|
|
657
|
+
font-size: 12px;
|
|
658
|
+
color: var(--accent-amber);
|
|
659
|
+
display: flex;
|
|
660
|
+
align-items: center;
|
|
661
|
+
gap: 6px;
|
|
662
|
+
flex-shrink: 0;
|
|
663
|
+
background: rgba(251, 191, 36, 0.08);
|
|
664
|
+
padding: 4px 8px;
|
|
665
|
+
border-radius: 4px;
|
|
666
|
+
border: 1px solid rgba(251, 191, 36, 0.2);
|
|
667
|
+
}
|
|
668
|
+
.lms-stream-card {
|
|
669
|
+
background: var(--bg-card);
|
|
670
|
+
backdrop-filter: blur(20px);
|
|
671
|
+
-webkit-backdrop-filter: blur(20px);
|
|
672
|
+
border: 1px solid rgba(167, 139, 250, 0.25);
|
|
673
|
+
border-radius: var(--radius-md);
|
|
674
|
+
overflow: hidden;
|
|
675
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
|
|
676
|
+
display: flex;
|
|
677
|
+
flex-direction: column;
|
|
678
|
+
width: 100%;
|
|
679
|
+
box-sizing: border-box;
|
|
680
|
+
}
|
|
681
|
+
.lms-stream-header {
|
|
682
|
+
padding: 12px 16px;
|
|
683
|
+
background: rgba(18, 22, 34, 0.85);
|
|
684
|
+
display: flex;
|
|
685
|
+
align-items: center;
|
|
686
|
+
justify-content: space-between;
|
|
687
|
+
flex-wrap: wrap;
|
|
688
|
+
gap: 10px;
|
|
689
|
+
cursor: pointer;
|
|
690
|
+
user-select: none;
|
|
691
|
+
border-bottom: 1px solid var(--border-glass);
|
|
692
|
+
}
|
|
693
|
+
.lms-header-left {
|
|
694
|
+
display: flex;
|
|
695
|
+
align-items: center;
|
|
696
|
+
gap: 8px;
|
|
697
|
+
}
|
|
698
|
+
.lms-pulse-dot {
|
|
699
|
+
width: 8px;
|
|
700
|
+
height: 8px;
|
|
701
|
+
border-radius: 50%;
|
|
702
|
+
background: var(--accent-emerald);
|
|
703
|
+
box-shadow: 0 0 10px var(--accent-emerald);
|
|
704
|
+
animation: pulse 1.8s infinite;
|
|
705
|
+
}
|
|
706
|
+
.lms-pulse-dot.offline {
|
|
707
|
+
background: var(--text-dim);
|
|
708
|
+
box-shadow: none;
|
|
709
|
+
animation: none;
|
|
710
|
+
}
|
|
711
|
+
.lms-title {
|
|
712
|
+
font-size: 13.5px;
|
|
713
|
+
font-weight: 700;
|
|
714
|
+
color: #fff;
|
|
715
|
+
letter-spacing: -0.01em;
|
|
716
|
+
}
|
|
717
|
+
.lms-tag {
|
|
718
|
+
font-family: var(--font-mono);
|
|
719
|
+
font-size: 10.5px;
|
|
720
|
+
padding: 2px 7px;
|
|
721
|
+
border-radius: 999px;
|
|
722
|
+
background: rgba(167, 139, 250, 0.15);
|
|
723
|
+
border: 1px solid rgba(167, 139, 250, 0.3);
|
|
724
|
+
color: var(--accent-violet);
|
|
725
|
+
}
|
|
726
|
+
.lms-header-right {
|
|
727
|
+
display: flex;
|
|
728
|
+
align-items: center;
|
|
729
|
+
gap: 8px;
|
|
730
|
+
flex-wrap: wrap;
|
|
731
|
+
}
|
|
732
|
+
.lms-filters {
|
|
733
|
+
display: flex;
|
|
734
|
+
align-items: center;
|
|
735
|
+
background: rgba(0, 0, 0, 0.4);
|
|
736
|
+
border: 1px solid var(--border-glass);
|
|
737
|
+
border-radius: 6px;
|
|
738
|
+
padding: 2px;
|
|
739
|
+
gap: 2px;
|
|
740
|
+
}
|
|
741
|
+
.lms-filter-btn {
|
|
742
|
+
background: transparent;
|
|
743
|
+
border: none;
|
|
744
|
+
color: var(--text-muted);
|
|
745
|
+
font-size: 11px;
|
|
746
|
+
font-weight: 500;
|
|
747
|
+
padding: 3px 8px;
|
|
748
|
+
border-radius: 4px;
|
|
749
|
+
cursor: pointer;
|
|
750
|
+
transition: all 0.15s;
|
|
751
|
+
}
|
|
752
|
+
.lms-filter-btn.active {
|
|
753
|
+
background: var(--accent-violet);
|
|
754
|
+
color: #fff;
|
|
755
|
+
font-weight: 600;
|
|
756
|
+
}
|
|
757
|
+
.lms-tool-btn {
|
|
758
|
+
background: rgba(255, 255, 255, 0.06);
|
|
759
|
+
border: 1px solid var(--border-glass);
|
|
760
|
+
color: var(--text-muted);
|
|
761
|
+
font-size: 11px;
|
|
762
|
+
font-weight: 500;
|
|
763
|
+
padding: 4px 10px;
|
|
764
|
+
border-radius: 6px;
|
|
765
|
+
cursor: pointer;
|
|
766
|
+
transition: all 0.15s;
|
|
767
|
+
}
|
|
768
|
+
.lms-tool-btn:hover {
|
|
769
|
+
color: #fff;
|
|
770
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
771
|
+
}
|
|
772
|
+
.lms-stream-body {
|
|
773
|
+
display: flex;
|
|
774
|
+
flex-direction: column;
|
|
775
|
+
background: #05070c;
|
|
776
|
+
}
|
|
777
|
+
.lms-telemetry-bar {
|
|
778
|
+
display: flex;
|
|
779
|
+
align-items: center;
|
|
780
|
+
gap: 16px;
|
|
781
|
+
padding: 8px 16px;
|
|
782
|
+
background: rgba(12, 16, 26, 0.9);
|
|
783
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
784
|
+
overflow-x: auto;
|
|
785
|
+
}
|
|
786
|
+
.lms-telemetry-item {
|
|
787
|
+
display: flex;
|
|
788
|
+
align-items: baseline;
|
|
789
|
+
gap: 6px;
|
|
790
|
+
white-space: nowrap;
|
|
791
|
+
}
|
|
792
|
+
.lms-telemetry-label {
|
|
793
|
+
font-size: 10px;
|
|
794
|
+
color: var(--text-dim);
|
|
795
|
+
text-transform: uppercase;
|
|
796
|
+
font-weight: 600;
|
|
797
|
+
letter-spacing: 0.03em;
|
|
798
|
+
}
|
|
799
|
+
.lms-telemetry-val {
|
|
800
|
+
font-family: var(--font-mono);
|
|
801
|
+
font-size: 12px;
|
|
802
|
+
font-weight: 700;
|
|
803
|
+
color: var(--accent-cyan);
|
|
804
|
+
}
|
|
805
|
+
.lms-log-viewport {
|
|
806
|
+
height: 240px;
|
|
807
|
+
overflow-y: auto;
|
|
808
|
+
padding: 12px 16px;
|
|
809
|
+
display: flex;
|
|
810
|
+
flex-direction: column;
|
|
811
|
+
gap: 8px;
|
|
812
|
+
font-family: var(--font-mono);
|
|
813
|
+
font-size: 11.5px;
|
|
814
|
+
line-height: 1.5;
|
|
815
|
+
}
|
|
816
|
+
.lms-log-empty {
|
|
817
|
+
color: var(--text-dim);
|
|
818
|
+
font-style: italic;
|
|
819
|
+
text-align: center;
|
|
820
|
+
margin: auto 0;
|
|
821
|
+
flex-shrink: 0;
|
|
822
|
+
}
|
|
823
|
+
.lms-entry-thought {
|
|
824
|
+
background: rgba(167, 139, 250, 0.08);
|
|
825
|
+
border-left: 3px solid var(--accent-violet);
|
|
826
|
+
padding: 6px 10px;
|
|
827
|
+
border-radius: 0 6px 6px 0;
|
|
828
|
+
color: #e2e8f0;
|
|
829
|
+
white-space: pre-wrap;
|
|
830
|
+
word-break: break-word;
|
|
831
|
+
flex-shrink: 0;
|
|
832
|
+
}
|
|
833
|
+
.lms-entry-tool {
|
|
834
|
+
background: rgba(251, 191, 36, 0.08);
|
|
835
|
+
border-left: 3px solid var(--accent-amber);
|
|
836
|
+
padding: 6px 10px;
|
|
837
|
+
border-radius: 0 6px 6px 0;
|
|
838
|
+
color: #fef08a;
|
|
839
|
+
white-space: pre-wrap;
|
|
840
|
+
word-break: break-word;
|
|
841
|
+
flex-shrink: 0;
|
|
842
|
+
}
|
|
843
|
+
.lms-entry-text {
|
|
844
|
+
color: #94a3b8;
|
|
845
|
+
white-space: pre-wrap;
|
|
846
|
+
word-break: break-word;
|
|
847
|
+
flex-shrink: 0;
|
|
848
|
+
}
|
|
849
|
+
.lms-entry-raw {
|
|
850
|
+
color: #64748b;
|
|
851
|
+
font-size: 10.5px;
|
|
852
|
+
white-space: pre-wrap;
|
|
853
|
+
word-break: break-all;
|
|
854
|
+
flex-shrink: 0;
|
|
855
|
+
}
|
|
856
|
+
.lms-thought-badge {
|
|
857
|
+
display: inline-block;
|
|
858
|
+
font-size: 9.5px;
|
|
859
|
+
font-weight: 700;
|
|
860
|
+
padding: 1px 5px;
|
|
861
|
+
border-radius: 3px;
|
|
862
|
+
background: rgba(167, 139, 250, 0.25);
|
|
863
|
+
color: var(--accent-violet);
|
|
864
|
+
margin-bottom: 4px;
|
|
865
|
+
text-transform: uppercase;
|
|
866
|
+
}
|
|
867
|
+
.lms-tool-badge {
|
|
868
|
+
display: inline-block;
|
|
869
|
+
font-size: 9.5px;
|
|
870
|
+
font-weight: 700;
|
|
871
|
+
padding: 1px 5px;
|
|
872
|
+
border-radius: 3px;
|
|
873
|
+
background: rgba(251, 191, 36, 0.25);
|
|
874
|
+
color: var(--accent-amber);
|
|
875
|
+
margin-bottom: 4px;
|
|
876
|
+
text-transform: uppercase;
|
|
877
|
+
}
|
|
878
|
+
.lms-entry-input {
|
|
879
|
+
background: rgba(56, 189, 248, 0.08);
|
|
880
|
+
border-left: 3px solid var(--accent-cyan);
|
|
881
|
+
padding: 6px 10px;
|
|
882
|
+
border-radius: 0 6px 6px 0;
|
|
883
|
+
color: #bae6fd;
|
|
884
|
+
white-space: pre-wrap;
|
|
885
|
+
word-break: break-word;
|
|
886
|
+
flex-shrink: 0;
|
|
887
|
+
}
|
|
888
|
+
.lms-input-badge {
|
|
889
|
+
display: inline-block;
|
|
890
|
+
font-size: 9.5px;
|
|
891
|
+
font-weight: 700;
|
|
892
|
+
padding: 1px 5px;
|
|
893
|
+
border-radius: 3px;
|
|
894
|
+
background: rgba(56, 189, 248, 0.25);
|
|
895
|
+
color: var(--accent-cyan);
|
|
896
|
+
margin-bottom: 4px;
|
|
897
|
+
text-transform: uppercase;
|
|
898
|
+
}
|
|
899
|
+
.lms-entry-answer {
|
|
900
|
+
background: rgba(52, 211, 153, 0.08);
|
|
901
|
+
border-left: 3px solid var(--accent-emerald);
|
|
902
|
+
padding: 6px 10px;
|
|
903
|
+
border-radius: 0 6px 6px 0;
|
|
904
|
+
color: #d1fae5;
|
|
905
|
+
white-space: pre-wrap;
|
|
906
|
+
word-break: break-word;
|
|
907
|
+
flex-shrink: 0;
|
|
908
|
+
}
|
|
909
|
+
.lms-live-badge {
|
|
910
|
+
font-size: 10px;
|
|
911
|
+
font-weight: 700;
|
|
912
|
+
padding: 2px 7px;
|
|
913
|
+
border-radius: 999px;
|
|
914
|
+
background: rgba(52, 211, 153, 0.15);
|
|
915
|
+
border: 1px solid rgba(52, 211, 153, 0.35);
|
|
916
|
+
color: var(--accent-emerald);
|
|
917
|
+
letter-spacing: 0.05em;
|
|
918
|
+
text-transform: uppercase;
|
|
919
|
+
}
|
|
920
|
+
.lms-answer-badge {
|
|
921
|
+
display: inline-block;
|
|
922
|
+
font-size: 9.5px;
|
|
923
|
+
font-weight: 700;
|
|
924
|
+
padding: 1px 5px;
|
|
925
|
+
border-radius: 3px;
|
|
926
|
+
background: rgba(52, 211, 153, 0.25);
|
|
927
|
+
color: var(--accent-emerald);
|
|
928
|
+
margin-bottom: 4px;
|
|
929
|
+
text-transform: uppercase;
|
|
930
|
+
}
|
|
931
|
+
.gate-banner {
|
|
932
|
+
padding: 12px 16px;
|
|
933
|
+
border-radius: var(--radius-md);
|
|
934
|
+
background: var(--bg-card);
|
|
935
|
+
border: 1px solid var(--border-glass);
|
|
936
|
+
display: flex;
|
|
937
|
+
flex-direction: column;
|
|
938
|
+
gap: 8px;
|
|
939
|
+
width: 100%;
|
|
940
|
+
max-width: 100%;
|
|
941
|
+
min-width: 0;
|
|
942
|
+
box-sizing: border-box;
|
|
943
|
+
overflow: hidden;
|
|
944
|
+
}
|
|
945
|
+
@media (min-width: 640px) {
|
|
946
|
+
.gate-banner {
|
|
947
|
+
flex-direction: row;
|
|
948
|
+
align-items: center;
|
|
949
|
+
justify-content: space-between;
|
|
950
|
+
padding: 14px 20px;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
.gate-title-group {
|
|
954
|
+
display: flex;
|
|
955
|
+
align-items: center;
|
|
956
|
+
gap: 8px;
|
|
957
|
+
font-size: 13px;
|
|
958
|
+
font-weight: 600;
|
|
959
|
+
}
|
|
960
|
+
.kpi-grid {
|
|
961
|
+
display: grid;
|
|
962
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
963
|
+
gap: 8px;
|
|
964
|
+
width: 100%;
|
|
965
|
+
max-width: 100%;
|
|
966
|
+
min-width: 0;
|
|
967
|
+
box-sizing: border-box;
|
|
968
|
+
}
|
|
969
|
+
@media (min-width: 640px) {
|
|
970
|
+
.kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
|
|
971
|
+
}
|
|
972
|
+
@media (min-width: 1024px) {
|
|
973
|
+
.kpi-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 16px; }
|
|
974
|
+
}
|
|
975
|
+
.kpi-card {
|
|
976
|
+
background: var(--bg-card);
|
|
977
|
+
backdrop-filter: blur(16px);
|
|
978
|
+
-webkit-backdrop-filter: blur(16px);
|
|
979
|
+
border: 1px solid var(--border-glass);
|
|
980
|
+
border-radius: var(--radius-md);
|
|
981
|
+
padding: 12px 10px;
|
|
982
|
+
display: flex;
|
|
983
|
+
flex-direction: column;
|
|
984
|
+
gap: 4px;
|
|
985
|
+
transition: transform 0.2s, border-color 0.2s;
|
|
986
|
+
min-width: 0;
|
|
987
|
+
width: 100%;
|
|
988
|
+
max-width: 100%;
|
|
989
|
+
box-sizing: border-box;
|
|
990
|
+
overflow: hidden;
|
|
991
|
+
}
|
|
992
|
+
@media (min-width: 640px) {
|
|
993
|
+
.kpi-card { padding: 18px; gap: 8px; }
|
|
994
|
+
}
|
|
995
|
+
.kpi-card:hover {
|
|
996
|
+
transform: translateY(-2px);
|
|
997
|
+
border-color: rgba(255, 255, 255, 0.16);
|
|
998
|
+
}
|
|
999
|
+
.kpi-label-row {
|
|
1000
|
+
display: flex;
|
|
1001
|
+
align-items: center;
|
|
1002
|
+
justify-content: space-between;
|
|
1003
|
+
font-size: 11px;
|
|
1004
|
+
color: var(--text-muted);
|
|
1005
|
+
font-weight: 500;
|
|
1006
|
+
}
|
|
1007
|
+
.kpi-target-tag {
|
|
1008
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1009
|
+
font-size: 10px;
|
|
1010
|
+
color: var(--text-dim);
|
|
1011
|
+
}
|
|
1012
|
+
.kpi-metric-val {
|
|
1013
|
+
font-family: var(--font-main);
|
|
1014
|
+
font-size: 20px;
|
|
1015
|
+
font-weight: 700;
|
|
1016
|
+
letter-spacing: -0.02em;
|
|
1017
|
+
}
|
|
1018
|
+
@media (min-width: 640px) {
|
|
1019
|
+
.kpi-metric-val { font-size: 26px; }
|
|
1020
|
+
}
|
|
1021
|
+
.kpi-bottom-row {
|
|
1022
|
+
display: flex;
|
|
1023
|
+
align-items: center;
|
|
1024
|
+
justify-content: space-between;
|
|
1025
|
+
font-size: 10px;
|
|
1026
|
+
color: var(--text-dim);
|
|
1027
|
+
}
|
|
1028
|
+
.badge-gate {
|
|
1029
|
+
font-size: 9px;
|
|
1030
|
+
font-weight: 700;
|
|
1031
|
+
padding: 2px 6px;
|
|
1032
|
+
border-radius: 4px;
|
|
1033
|
+
text-transform: uppercase;
|
|
1034
|
+
letter-spacing: 0.05em;
|
|
1035
|
+
}
|
|
1036
|
+
.badge-pass { background: var(--accent-emerald-dim); color: var(--accent-emerald); border: 1px solid rgba(52, 211, 153, 0.3); }
|
|
1037
|
+
.badge-fail { background: var(--accent-red-dim); color: var(--accent-red); border: 1px solid rgba(248, 113, 113, 0.3); }
|
|
1038
|
+
.badge-neutral { background: rgba(255, 255, 255, 0.06); color: var(--text-dim); }
|
|
1039
|
+
.btn-run-case {
|
|
1040
|
+
padding: 5px 12px;
|
|
1041
|
+
font-size: 11.5px;
|
|
1042
|
+
font-weight: 600;
|
|
1043
|
+
border-radius: 999px;
|
|
1044
|
+
background: var(--accent-rose-dim);
|
|
1045
|
+
color: var(--accent-rose);
|
|
1046
|
+
border: 1px solid rgba(244, 114, 182, 0.35);
|
|
1047
|
+
cursor: pointer;
|
|
1048
|
+
display: inline-flex;
|
|
1049
|
+
align-items: center;
|
|
1050
|
+
gap: 4px;
|
|
1051
|
+
transition: all 0.15s ease;
|
|
1052
|
+
white-space: nowrap;
|
|
1053
|
+
min-height: 30px;
|
|
1054
|
+
touch-action: manipulation;
|
|
1055
|
+
}
|
|
1056
|
+
.btn-run-case:hover, .btn-run-case:active {
|
|
1057
|
+
background: var(--accent-rose);
|
|
1058
|
+
color: #fff;
|
|
1059
|
+
box-shadow: 0 0 10px var(--accent-rose-glow);
|
|
1060
|
+
}
|
|
1061
|
+
.btn-run-case:disabled {
|
|
1062
|
+
opacity: 0.4;
|
|
1063
|
+
cursor: not-allowed;
|
|
1064
|
+
}
|
|
1065
|
+
.explorer-card {
|
|
1066
|
+
background: var(--bg-card);
|
|
1067
|
+
backdrop-filter: blur(20px);
|
|
1068
|
+
-webkit-backdrop-filter: blur(20px);
|
|
1069
|
+
border: 1px solid var(--border-glass);
|
|
1070
|
+
border-radius: var(--radius-md);
|
|
1071
|
+
display: flex;
|
|
1072
|
+
flex-direction: column;
|
|
1073
|
+
overflow: hidden;
|
|
1074
|
+
width: 100%;
|
|
1075
|
+
max-width: 100%;
|
|
1076
|
+
min-width: 0;
|
|
1077
|
+
box-sizing: border-box;
|
|
1078
|
+
}
|
|
1079
|
+
.explorer-toolbar {
|
|
1080
|
+
padding: 12px 16px;
|
|
1081
|
+
border-bottom: 1px solid var(--border-glass);
|
|
1082
|
+
display: flex;
|
|
1083
|
+
flex-direction: column;
|
|
1084
|
+
gap: 10px;
|
|
1085
|
+
width: 100%;
|
|
1086
|
+
max-width: 100%;
|
|
1087
|
+
min-width: 0;
|
|
1088
|
+
box-sizing: border-box;
|
|
1089
|
+
overflow: hidden;
|
|
1090
|
+
}
|
|
1091
|
+
@media (min-width: 768px) {
|
|
1092
|
+
.explorer-toolbar {
|
|
1093
|
+
flex-direction: row;
|
|
1094
|
+
align-items: center;
|
|
1095
|
+
justify-content: space-between;
|
|
1096
|
+
padding: 16px 20px;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
.filter-tabs {
|
|
1100
|
+
display: flex;
|
|
1101
|
+
align-items: center;
|
|
1102
|
+
gap: 4px;
|
|
1103
|
+
overflow-x: auto;
|
|
1104
|
+
scrollbar-width: none;
|
|
1105
|
+
-webkit-overflow-scrolling: touch;
|
|
1106
|
+
padding-bottom: 2px;
|
|
1107
|
+
width: 100%;
|
|
1108
|
+
max-width: 100%;
|
|
1109
|
+
min-width: 0;
|
|
1110
|
+
box-sizing: border-box;
|
|
1111
|
+
}
|
|
1112
|
+
.filter-tabs::-webkit-scrollbar { display: none; }
|
|
1113
|
+
.filter-tab {
|
|
1114
|
+
padding: 6px 10px;
|
|
1115
|
+
font-size: 11px;
|
|
1116
|
+
font-weight: 500;
|
|
1117
|
+
border-radius: 999px;
|
|
1118
|
+
background: transparent;
|
|
1119
|
+
border: 1px solid transparent;
|
|
1120
|
+
color: var(--text-muted);
|
|
1121
|
+
cursor: pointer;
|
|
1122
|
+
white-space: nowrap;
|
|
1123
|
+
transition: all 0.15s;
|
|
1124
|
+
}
|
|
1125
|
+
.filter-tab:hover { color: var(--text-main); background: rgba(255, 255, 255, 0.05); }
|
|
1126
|
+
.filter-tab.active {
|
|
1127
|
+
background: var(--accent-rose-dim);
|
|
1128
|
+
color: var(--accent-rose);
|
|
1129
|
+
border-color: rgba(244, 114, 182, 0.35);
|
|
1130
|
+
font-weight: 600;
|
|
1131
|
+
}
|
|
1132
|
+
.search-row {
|
|
1133
|
+
display: flex;
|
|
1134
|
+
align-items: center;
|
|
1135
|
+
gap: 8px;
|
|
1136
|
+
width: 100%;
|
|
1137
|
+
min-width: 0;
|
|
1138
|
+
max-width: 100%;
|
|
1139
|
+
box-sizing: border-box;
|
|
1140
|
+
}
|
|
1141
|
+
@media (min-width: 768px) {
|
|
1142
|
+
.search-row { width: auto; }
|
|
1143
|
+
}
|
|
1144
|
+
.search-input {
|
|
1145
|
+
background: rgba(8, 10, 15, 0.8);
|
|
1146
|
+
border: 1px solid var(--border-glass);
|
|
1147
|
+
color: var(--text-main);
|
|
1148
|
+
padding: 8px 14px;
|
|
1149
|
+
border-radius: 999px;
|
|
1150
|
+
font-size: 12px;
|
|
1151
|
+
flex: 1;
|
|
1152
|
+
min-width: 0;
|
|
1153
|
+
max-width: 100%;
|
|
1154
|
+
outline: none;
|
|
1155
|
+
min-height: 36px;
|
|
1156
|
+
box-sizing: border-box;
|
|
1157
|
+
}
|
|
1158
|
+
.search-input:focus { border-color: var(--accent-rose); }
|
|
1159
|
+
.view-toggle-btn {
|
|
1160
|
+
padding: 6px 10px;
|
|
1161
|
+
background: var(--bg-glass);
|
|
1162
|
+
border: 1px solid var(--border-glass);
|
|
1163
|
+
border-radius: var(--radius-sm);
|
|
1164
|
+
color: var(--text-muted);
|
|
1165
|
+
cursor: pointer;
|
|
1166
|
+
display: inline-flex;
|
|
1167
|
+
align-items: center;
|
|
1168
|
+
justify-content: center;
|
|
1169
|
+
min-height: 36px;
|
|
1170
|
+
}
|
|
1171
|
+
.view-toggle-btn:hover { color: var(--text-main); }
|
|
1172
|
+
.table-view-container {
|
|
1173
|
+
display: none;
|
|
1174
|
+
overflow-x: auto;
|
|
1175
|
+
max-height: 600px;
|
|
1176
|
+
overflow-y: auto;
|
|
1177
|
+
-webkit-overflow-scrolling: touch;
|
|
1178
|
+
}
|
|
1179
|
+
@media (min-width: 1024px) {
|
|
1180
|
+
.table-view-container { display: block; }
|
|
1181
|
+
}
|
|
1182
|
+
table {
|
|
1183
|
+
width: 100%;
|
|
1184
|
+
border-collapse: collapse;
|
|
1185
|
+
font-size: 13px;
|
|
1186
|
+
text-align: left;
|
|
1187
|
+
}
|
|
1188
|
+
th {
|
|
1189
|
+
position: sticky;
|
|
1190
|
+
top: 0;
|
|
1191
|
+
background: rgba(14, 18, 28, 0.95);
|
|
1192
|
+
backdrop-filter: blur(10px);
|
|
1193
|
+
padding: 12px 14px;
|
|
1194
|
+
font-size: 11px;
|
|
1195
|
+
font-weight: 600;
|
|
1196
|
+
color: var(--text-dim);
|
|
1197
|
+
text-transform: uppercase;
|
|
1198
|
+
letter-spacing: 0.05em;
|
|
1199
|
+
border-bottom: 1px solid var(--border-glass);
|
|
1200
|
+
z-index: 10;
|
|
1201
|
+
}
|
|
1202
|
+
td {
|
|
1203
|
+
padding: 12px 14px;
|
|
1204
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
|
1205
|
+
vertical-align: middle;
|
|
1206
|
+
}
|
|
1207
|
+
tr.case-row {
|
|
1208
|
+
cursor: pointer;
|
|
1209
|
+
transition: background-color 0.15s;
|
|
1210
|
+
}
|
|
1211
|
+
tr.case-row:hover {
|
|
1212
|
+
background-color: rgba(244, 114, 182, 0.06);
|
|
1213
|
+
}
|
|
1214
|
+
.mobile-cards-container {
|
|
1215
|
+
display: flex;
|
|
1216
|
+
flex-direction: column;
|
|
1217
|
+
gap: 10px;
|
|
1218
|
+
padding: 12px 10px;
|
|
1219
|
+
overflow-y: visible;
|
|
1220
|
+
-webkit-overflow-scrolling: touch;
|
|
1221
|
+
}
|
|
1222
|
+
@media (min-width: 1024px) {
|
|
1223
|
+
.mobile-cards-container { display: none; }
|
|
1224
|
+
}
|
|
1225
|
+
.mobile-case-card {
|
|
1226
|
+
background: rgba(12, 16, 26, 0.85);
|
|
1227
|
+
backdrop-filter: blur(12px);
|
|
1228
|
+
-webkit-backdrop-filter: blur(12px);
|
|
1229
|
+
border: 1px solid var(--border-glass);
|
|
1230
|
+
border-radius: var(--radius-md);
|
|
1231
|
+
padding: 14px;
|
|
1232
|
+
display: flex;
|
|
1233
|
+
flex-direction: column;
|
|
1234
|
+
gap: 9px;
|
|
1235
|
+
cursor: pointer;
|
|
1236
|
+
transition: transform 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
|
|
1237
|
+
touch-action: manipulation;
|
|
1238
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
|
|
1239
|
+
}
|
|
1240
|
+
.mobile-case-card:active {
|
|
1241
|
+
transform: scale(0.985);
|
|
1242
|
+
border-color: rgba(244, 114, 182, 0.45);
|
|
1243
|
+
background: rgba(244, 114, 182, 0.08);
|
|
1244
|
+
}
|
|
1245
|
+
.mobile-card-top {
|
|
1246
|
+
display: flex;
|
|
1247
|
+
align-items: center;
|
|
1248
|
+
justify-content: space-between;
|
|
1249
|
+
gap: 8px;
|
|
1250
|
+
}
|
|
1251
|
+
.mobile-card-question {
|
|
1252
|
+
font-size: 13.5px;
|
|
1253
|
+
line-height: 1.45;
|
|
1254
|
+
color: #f1f5f9;
|
|
1255
|
+
display: -webkit-box;
|
|
1256
|
+
-webkit-line-clamp: 2;
|
|
1257
|
+
-webkit-box-orient: vertical;
|
|
1258
|
+
overflow: hidden;
|
|
1259
|
+
}
|
|
1260
|
+
.mobile-card-stats {
|
|
1261
|
+
display: flex;
|
|
1262
|
+
align-items: center;
|
|
1263
|
+
gap: 8px;
|
|
1264
|
+
font-size: 11.5px;
|
|
1265
|
+
color: var(--text-muted);
|
|
1266
|
+
flex-wrap: wrap;
|
|
1267
|
+
}
|
|
1268
|
+
.pill {
|
|
1269
|
+
display: inline-flex;
|
|
1270
|
+
align-items: center;
|
|
1271
|
+
gap: 4px;
|
|
1272
|
+
padding: 2px 7px;
|
|
1273
|
+
border-radius: 4px;
|
|
1274
|
+
font-size: 11px;
|
|
1275
|
+
font-weight: 500;
|
|
1276
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1277
|
+
}
|
|
1278
|
+
.pill-success { background: var(--accent-emerald-dim); color: var(--accent-emerald); }
|
|
1279
|
+
.pill-error { background: var(--accent-red-dim); color: var(--accent-red); }
|
|
1280
|
+
.pill-warn { background: var(--accent-amber-dim); color: var(--accent-amber); }
|
|
1281
|
+
.pill-tag { background: rgba(255, 255, 255, 0.06); color: var(--text-muted); }
|
|
1282
|
+
.drawer-overlay {
|
|
1283
|
+
position: fixed;
|
|
1284
|
+
inset: 0;
|
|
1285
|
+
background: rgba(0, 0, 0, 0.7);
|
|
1286
|
+
backdrop-filter: blur(5px);
|
|
1287
|
+
-webkit-backdrop-filter: blur(5px);
|
|
1288
|
+
z-index: 50;
|
|
1289
|
+
opacity: 0;
|
|
1290
|
+
pointer-events: none;
|
|
1291
|
+
transition: opacity 0.25s ease;
|
|
1292
|
+
}
|
|
1293
|
+
.drawer-overlay.open {
|
|
1294
|
+
opacity: 1;
|
|
1295
|
+
pointer-events: auto;
|
|
1296
|
+
}
|
|
1297
|
+
.drawer {
|
|
1298
|
+
position: fixed;
|
|
1299
|
+
z-index: 60;
|
|
1300
|
+
background: #0c0f18;
|
|
1301
|
+
border: 1px solid var(--border-glass);
|
|
1302
|
+
display: flex;
|
|
1303
|
+
flex-direction: column;
|
|
1304
|
+
box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.8);
|
|
1305
|
+
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1306
|
+
left: 0;
|
|
1307
|
+
right: 0;
|
|
1308
|
+
bottom: 0;
|
|
1309
|
+
height: 85vh;
|
|
1310
|
+
height: 85dvh;
|
|
1311
|
+
border-radius: 20px 20px 0 0;
|
|
1312
|
+
transform: translateY(100%);
|
|
1313
|
+
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
|
1314
|
+
}
|
|
1315
|
+
@media (min-width: 768px) {
|
|
1316
|
+
.drawer {
|
|
1317
|
+
top: 0;
|
|
1318
|
+
right: 0;
|
|
1319
|
+
bottom: 0;
|
|
1320
|
+
left: auto;
|
|
1321
|
+
width: min(720px, 90vw);
|
|
1322
|
+
height: 100%;
|
|
1323
|
+
border-radius: 0;
|
|
1324
|
+
border-left: 1px solid var(--border-glass);
|
|
1325
|
+
transform: translateX(100%);
|
|
1326
|
+
box-shadow: -10px 0 40px rgba(0, 0, 0, 0.7);
|
|
1327
|
+
padding-bottom: 0;
|
|
1328
|
+
}
|
|
1329
|
+
.drawer.open {
|
|
1330
|
+
transform: translateX(0);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
.drawer.open {
|
|
1334
|
+
transform: translateY(0);
|
|
1335
|
+
}
|
|
1336
|
+
@media (min-width: 768px) {
|
|
1337
|
+
.drawer.open { transform: translateX(0); }
|
|
1338
|
+
}
|
|
1339
|
+
.drawer-drag-handle {
|
|
1340
|
+
width: 44px;
|
|
1341
|
+
height: 5px;
|
|
1342
|
+
background: rgba(255, 255, 255, 0.25);
|
|
1343
|
+
border-radius: 999px;
|
|
1344
|
+
margin: 10px auto 4px;
|
|
1345
|
+
cursor: grab;
|
|
1346
|
+
touch-action: none;
|
|
1347
|
+
}
|
|
1348
|
+
@media (min-width: 768px) {
|
|
1349
|
+
.drawer-drag-handle { display: none; }
|
|
1350
|
+
}
|
|
1351
|
+
.drawer-header {
|
|
1352
|
+
padding: 14px 18px;
|
|
1353
|
+
border-bottom: 1px solid var(--border-glass);
|
|
1354
|
+
display: flex;
|
|
1355
|
+
align-items: center;
|
|
1356
|
+
justify-content: space-between;
|
|
1357
|
+
gap: 12px;
|
|
1358
|
+
}
|
|
1359
|
+
@media (min-width: 640px) {
|
|
1360
|
+
.drawer-header { padding: 18px 24px; }
|
|
1361
|
+
}
|
|
1362
|
+
.drawer-title-area {
|
|
1363
|
+
display: flex;
|
|
1364
|
+
align-items: center;
|
|
1365
|
+
gap: 8px;
|
|
1366
|
+
overflow: hidden;
|
|
1367
|
+
}
|
|
1368
|
+
.drawer-title {
|
|
1369
|
+
font-family: var(--font-main);
|
|
1370
|
+
font-size: 16px;
|
|
1371
|
+
font-weight: 600;
|
|
1372
|
+
overflow: hidden;
|
|
1373
|
+
text-overflow: ellipsis;
|
|
1374
|
+
white-space: nowrap;
|
|
1375
|
+
}
|
|
1376
|
+
@media (min-width: 640px) {
|
|
1377
|
+
.drawer-title { font-size: 18px; }
|
|
1378
|
+
}
|
|
1379
|
+
.drawer-close {
|
|
1380
|
+
background: rgba(255, 255, 255, 0.06);
|
|
1381
|
+
border: 1px solid var(--border-glass);
|
|
1382
|
+
color: var(--text-muted);
|
|
1383
|
+
width: 34px;
|
|
1384
|
+
height: 34px;
|
|
1385
|
+
border-radius: 50%;
|
|
1386
|
+
display: flex;
|
|
1387
|
+
align-items: center;
|
|
1388
|
+
justify-content: center;
|
|
1389
|
+
cursor: pointer;
|
|
1390
|
+
flex-shrink: 0;
|
|
1391
|
+
transition: all 0.15s;
|
|
1392
|
+
}
|
|
1393
|
+
.drawer-close:hover {
|
|
1394
|
+
background: rgba(255, 255, 255, 0.12);
|
|
1395
|
+
color: var(--text-main);
|
|
1396
|
+
}
|
|
1397
|
+
.drawer-body {
|
|
1398
|
+
flex: 1;
|
|
1399
|
+
overflow-y: auto;
|
|
1400
|
+
padding: 16px;
|
|
1401
|
+
display: flex;
|
|
1402
|
+
flex-direction: column;
|
|
1403
|
+
gap: 16px;
|
|
1404
|
+
-webkit-overflow-scrolling: touch;
|
|
1405
|
+
}
|
|
1406
|
+
@media (min-width: 640px) {
|
|
1407
|
+
.drawer-body { padding: 24px; gap: 20px; }
|
|
1408
|
+
}
|
|
1409
|
+
.detail-section {
|
|
1410
|
+
background: rgba(14, 18, 28, 0.7);
|
|
1411
|
+
border: 1px solid var(--border-glass);
|
|
1412
|
+
border-radius: var(--radius-sm);
|
|
1413
|
+
padding: 14px;
|
|
1414
|
+
display: flex;
|
|
1415
|
+
flex-direction: column;
|
|
1416
|
+
gap: 8px;
|
|
1417
|
+
}
|
|
1418
|
+
.detail-section-header {
|
|
1419
|
+
display: flex;
|
|
1420
|
+
align-items: center;
|
|
1421
|
+
justify-content: space-between;
|
|
1422
|
+
}
|
|
1423
|
+
.detail-label {
|
|
1424
|
+
font-size: 11px;
|
|
1425
|
+
font-weight: 600;
|
|
1426
|
+
color: var(--text-muted);
|
|
1427
|
+
text-transform: uppercase;
|
|
1428
|
+
letter-spacing: 0.05em;
|
|
1429
|
+
}
|
|
1430
|
+
.detail-content {
|
|
1431
|
+
font-size: 13px;
|
|
1432
|
+
line-height: 1.6;
|
|
1433
|
+
word-break: break-word;
|
|
1434
|
+
}
|
|
1435
|
+
.react-timeline {
|
|
1436
|
+
display: flex;
|
|
1437
|
+
flex-direction: column;
|
|
1438
|
+
gap: 10px;
|
|
1439
|
+
position: relative;
|
|
1440
|
+
padding-left: 18px;
|
|
1441
|
+
}
|
|
1442
|
+
.react-timeline::before {
|
|
1443
|
+
content: '';
|
|
1444
|
+
position: absolute;
|
|
1445
|
+
left: 5px;
|
|
1446
|
+
top: 6px;
|
|
1447
|
+
bottom: 6px;
|
|
1448
|
+
width: 2px;
|
|
1449
|
+
background: var(--border-glass);
|
|
1450
|
+
}
|
|
1451
|
+
.timeline-step {
|
|
1452
|
+
position: relative;
|
|
1453
|
+
background: rgba(8, 10, 16, 0.75);
|
|
1454
|
+
border: 1px solid var(--border-glass);
|
|
1455
|
+
border-radius: var(--radius-sm);
|
|
1456
|
+
padding: 10px 12px;
|
|
1457
|
+
display: flex;
|
|
1458
|
+
flex-direction: column;
|
|
1459
|
+
gap: 6px;
|
|
1460
|
+
}
|
|
1461
|
+
.timeline-step::before {
|
|
1462
|
+
content: '';
|
|
1463
|
+
position: absolute;
|
|
1464
|
+
left: -17px;
|
|
1465
|
+
top: 13px;
|
|
1466
|
+
width: 8px;
|
|
1467
|
+
height: 8px;
|
|
1468
|
+
border-radius: 50%;
|
|
1469
|
+
background: var(--accent-violet);
|
|
1470
|
+
box-shadow: 0 0 6px var(--accent-violet);
|
|
1471
|
+
}
|
|
1472
|
+
pre.code-block {
|
|
1473
|
+
background: #05070c;
|
|
1474
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
1475
|
+
border-radius: 6px;
|
|
1476
|
+
padding: 10px;
|
|
1477
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1478
|
+
font-size: 11px;
|
|
1479
|
+
overflow-x: auto;
|
|
1480
|
+
color: #cbd5e1;
|
|
1481
|
+
max-height: 220px;
|
|
1482
|
+
}
|
|
1483
|
+
.copy-btn {
|
|
1484
|
+
background: rgba(255, 255, 255, 0.06);
|
|
1485
|
+
border: 1px solid var(--border-glass);
|
|
1486
|
+
color: var(--text-muted);
|
|
1487
|
+
border-radius: 6px;
|
|
1488
|
+
padding: 4px 10px;
|
|
1489
|
+
font-size: 11px;
|
|
1490
|
+
font-weight: 500;
|
|
1491
|
+
cursor: pointer;
|
|
1492
|
+
min-height: 28px;
|
|
1493
|
+
display: inline-flex;
|
|
1494
|
+
align-items: center;
|
|
1495
|
+
justify-content: center;
|
|
1496
|
+
transition: all 0.15s ease;
|
|
1497
|
+
touch-action: manipulation;
|
|
1498
|
+
}
|
|
1499
|
+
.copy-btn:hover { color: var(--text-main); border-color: rgba(255, 255, 255, 0.2); }
|
|
1500
|
+
.copy-btn:active { background: var(--accent-rose-dim); color: var(--accent-rose); }
|
|
1501
|
+
.sync-toast {
|
|
1502
|
+
position: fixed;
|
|
1503
|
+
bottom: max(16px, env(safe-area-inset-bottom));
|
|
1504
|
+
right: 16px;
|
|
1505
|
+
background: rgba(15, 20, 32, 0.96);
|
|
1506
|
+
backdrop-filter: blur(16px);
|
|
1507
|
+
-webkit-backdrop-filter: blur(16px);
|
|
1508
|
+
border: 1px solid rgba(244, 114, 182, 0.4);
|
|
1509
|
+
color: #fff;
|
|
1510
|
+
padding: 8px 14px;
|
|
1511
|
+
border-radius: 999px;
|
|
1512
|
+
font-size: 11.5px;
|
|
1513
|
+
font-weight: 500;
|
|
1514
|
+
display: flex;
|
|
1515
|
+
align-items: center;
|
|
1516
|
+
gap: 6px;
|
|
1517
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
|
1518
|
+
z-index: 99;
|
|
1519
|
+
opacity: 0;
|
|
1520
|
+
transform: translateY(12px);
|
|
1521
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
1522
|
+
pointer-events: none;
|
|
1523
|
+
}
|
|
1524
|
+
.sync-toast.show {
|
|
1525
|
+
opacity: 1;
|
|
1526
|
+
transform: translateY(0);
|
|
1527
|
+
}
|
|
1528
|
+
.studio-subnav {
|
|
1529
|
+
backdrop-filter: blur(20px);
|
|
1530
|
+
-webkit-backdrop-filter: blur(20px);
|
|
1531
|
+
background: rgba(14, 18, 28, 0.75);
|
|
1532
|
+
border-bottom: 1px solid var(--border-glass);
|
|
1533
|
+
padding: 8px 14px;
|
|
1534
|
+
display: flex;
|
|
1535
|
+
align-items: center;
|
|
1536
|
+
justify-content: space-between;
|
|
1537
|
+
gap: 12px;
|
|
1538
|
+
overflow-x: auto;
|
|
1539
|
+
white-space: nowrap;
|
|
1540
|
+
scrollbar-width: none;
|
|
1541
|
+
-webkit-overflow-scrolling: touch;
|
|
1542
|
+
position: sticky;
|
|
1543
|
+
top: 59px;
|
|
1544
|
+
z-index: 35;
|
|
1545
|
+
}
|
|
1546
|
+
.studio-subnav::-webkit-scrollbar { display: none; }
|
|
1547
|
+
.subnav-left, .subnav-right {
|
|
1548
|
+
display: flex;
|
|
1549
|
+
align-items: center;
|
|
1550
|
+
gap: 8px;
|
|
1551
|
+
}
|
|
1552
|
+
.nav-pill {
|
|
1553
|
+
display: inline-flex;
|
|
1554
|
+
align-items: center;
|
|
1555
|
+
gap: 6px;
|
|
1556
|
+
padding: 6px 12px;
|
|
1557
|
+
border-radius: 999px;
|
|
1558
|
+
font-size: 11.5px;
|
|
1559
|
+
font-weight: 500;
|
|
1560
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1561
|
+
border: 1px solid var(--border-glass);
|
|
1562
|
+
color: var(--text-muted);
|
|
1563
|
+
cursor: pointer;
|
|
1564
|
+
transition: all 0.15s ease;
|
|
1565
|
+
touch-action: manipulation;
|
|
1566
|
+
}
|
|
1567
|
+
.nav-pill:hover {
|
|
1568
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1569
|
+
color: var(--text-main);
|
|
1570
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
1571
|
+
}
|
|
1572
|
+
.nav-pill:active {
|
|
1573
|
+
transform: scale(0.98);
|
|
1574
|
+
}
|
|
1575
|
+
.export-group {
|
|
1576
|
+
display: flex;
|
|
1577
|
+
align-items: center;
|
|
1578
|
+
background: rgba(0, 0, 0, 0.35);
|
|
1579
|
+
border: 1px solid var(--border-glass);
|
|
1580
|
+
border-radius: 999px;
|
|
1581
|
+
padding: 2px;
|
|
1582
|
+
gap: 2px;
|
|
1583
|
+
}
|
|
1584
|
+
.export-btn {
|
|
1585
|
+
display: inline-flex;
|
|
1586
|
+
align-items: center;
|
|
1587
|
+
gap: 5px;
|
|
1588
|
+
padding: 5px 10px;
|
|
1589
|
+
border-radius: 999px;
|
|
1590
|
+
font-size: 11px;
|
|
1591
|
+
font-weight: 600;
|
|
1592
|
+
background: transparent;
|
|
1593
|
+
border: none;
|
|
1594
|
+
color: var(--text-muted);
|
|
1595
|
+
cursor: pointer;
|
|
1596
|
+
transition: all 0.15s ease;
|
|
1597
|
+
}
|
|
1598
|
+
.export-btn:hover {
|
|
1599
|
+
background: rgba(255, 255, 255, 0.12);
|
|
1600
|
+
color: var(--text-main);
|
|
1601
|
+
}
|
|
1602
|
+
.modal-overlay {
|
|
1603
|
+
position: fixed;
|
|
1604
|
+
inset: 0;
|
|
1605
|
+
background: rgba(0, 0, 0, 0.78);
|
|
1606
|
+
backdrop-filter: blur(14px);
|
|
1607
|
+
-webkit-backdrop-filter: blur(14px);
|
|
1608
|
+
z-index: 100;
|
|
1609
|
+
display: none;
|
|
1610
|
+
align-items: center;
|
|
1611
|
+
justify-content: center;
|
|
1612
|
+
padding: 16px;
|
|
1613
|
+
box-sizing: border-box;
|
|
1614
|
+
}
|
|
1615
|
+
.modal-overlay.open {
|
|
1616
|
+
display: flex;
|
|
1617
|
+
}
|
|
1618
|
+
.modal-window {
|
|
1619
|
+
background: #0d111a;
|
|
1620
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
1621
|
+
border-radius: var(--radius-md);
|
|
1622
|
+
width: 100%;
|
|
1623
|
+
max-width: 880px;
|
|
1624
|
+
max-height: 90vh;
|
|
1625
|
+
max-height: 90dvh;
|
|
1626
|
+
display: flex;
|
|
1627
|
+
flex-direction: column;
|
|
1628
|
+
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.85);
|
|
1629
|
+
overflow: hidden;
|
|
1630
|
+
animation: modalFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1631
|
+
}
|
|
1632
|
+
@keyframes modalFadeIn {
|
|
1633
|
+
from { opacity: 0; transform: scale(0.97) translateY(6px); }
|
|
1634
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
1635
|
+
}
|
|
1636
|
+
.modal-header {
|
|
1637
|
+
display: flex;
|
|
1638
|
+
align-items: center;
|
|
1639
|
+
justify-content: space-between;
|
|
1640
|
+
padding: 14px 18px;
|
|
1641
|
+
border-bottom: 1px solid var(--border-glass);
|
|
1642
|
+
background: rgba(18, 22, 34, 0.85);
|
|
1643
|
+
}
|
|
1644
|
+
.modal-title {
|
|
1645
|
+
display: flex;
|
|
1646
|
+
align-items: center;
|
|
1647
|
+
gap: 8px;
|
|
1648
|
+
font-size: 14px;
|
|
1649
|
+
font-weight: 700;
|
|
1650
|
+
color: var(--text-main);
|
|
1651
|
+
}
|
|
1652
|
+
.modal-close {
|
|
1653
|
+
background: transparent;
|
|
1654
|
+
border: none;
|
|
1655
|
+
color: var(--text-muted);
|
|
1656
|
+
cursor: pointer;
|
|
1657
|
+
font-size: 20px;
|
|
1658
|
+
line-height: 1;
|
|
1659
|
+
padding: 4px 8px;
|
|
1660
|
+
border-radius: var(--radius-sm);
|
|
1661
|
+
}
|
|
1662
|
+
.modal-close:hover {
|
|
1663
|
+
background: rgba(255, 255, 255, 0.08);
|
|
1664
|
+
color: var(--text-main);
|
|
1665
|
+
}
|
|
1666
|
+
.modal-body {
|
|
1667
|
+
padding: 18px;
|
|
1668
|
+
overflow-y: auto;
|
|
1669
|
+
flex: 1;
|
|
1670
|
+
display: flex;
|
|
1671
|
+
flex-direction: column;
|
|
1672
|
+
gap: 16px;
|
|
1673
|
+
}
|
|
1674
|
+
.modal-footer {
|
|
1675
|
+
padding: 12px 18px;
|
|
1676
|
+
border-top: 1px solid var(--border-glass);
|
|
1677
|
+
display: flex;
|
|
1678
|
+
align-items: center;
|
|
1679
|
+
justify-content: flex-end;
|
|
1680
|
+
gap: 10px;
|
|
1681
|
+
background: rgba(14, 18, 26, 0.6);
|
|
1682
|
+
}
|
|
1683
|
+
.waterfall-container {
|
|
1684
|
+
background: rgba(0, 0, 0, 0.3);
|
|
1685
|
+
border: 1px solid var(--border-glass);
|
|
1686
|
+
border-radius: var(--radius-sm);
|
|
1687
|
+
padding: 12px;
|
|
1688
|
+
display: flex;
|
|
1689
|
+
flex-direction: column;
|
|
1690
|
+
gap: 10px;
|
|
1691
|
+
}
|
|
1692
|
+
.waterfall-bar {
|
|
1693
|
+
height: 14px;
|
|
1694
|
+
border-radius: 999px;
|
|
1695
|
+
overflow: hidden;
|
|
1696
|
+
display: flex;
|
|
1697
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1698
|
+
}
|
|
1699
|
+
.waterfall-seg {
|
|
1700
|
+
height: 100%;
|
|
1701
|
+
transition: width 0.3s ease;
|
|
1702
|
+
}
|
|
1703
|
+
.waterfall-seg.seg-llm { background: var(--accent-violet); }
|
|
1704
|
+
.waterfall-seg.seg-tool { background: var(--accent-emerald); }
|
|
1705
|
+
.waterfall-seg.seg-net { background: rgba(255, 255, 255, 0.16); }
|
|
1706
|
+
.waterfall-legend {
|
|
1707
|
+
display: flex;
|
|
1708
|
+
flex-wrap: wrap;
|
|
1709
|
+
align-items: center;
|
|
1710
|
+
gap: 14px;
|
|
1711
|
+
font-size: 11px;
|
|
1712
|
+
color: var(--text-muted);
|
|
1713
|
+
}
|
|
1714
|
+
.waterfall-legend span {
|
|
1715
|
+
display: inline-flex;
|
|
1716
|
+
align-items: center;
|
|
1717
|
+
gap: 6px;
|
|
1718
|
+
}
|
|
1719
|
+
.legend-dot {
|
|
1720
|
+
width: 8px;
|
|
1721
|
+
height: 8px;
|
|
1722
|
+
border-radius: 50%;
|
|
1723
|
+
}
|
|
1724
|
+
.legend-dot.seg-llm { background: var(--accent-violet); }
|
|
1725
|
+
.legend-dot.seg-tool { background: var(--accent-emerald); }
|
|
1726
|
+
.legend-dot.seg-net { background: rgba(255, 255, 255, 0.3); }
|
|
1727
|
+
.token-grid {
|
|
1728
|
+
display: grid;
|
|
1729
|
+
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
1730
|
+
gap: 8px;
|
|
1731
|
+
}
|
|
1732
|
+
.token-stat-box {
|
|
1733
|
+
background: rgba(255, 255, 255, 0.03);
|
|
1734
|
+
border: 1px solid var(--border-glass);
|
|
1735
|
+
border-radius: var(--radius-sm);
|
|
1736
|
+
padding: 8px 10px;
|
|
1737
|
+
display: flex;
|
|
1738
|
+
flex-direction: column;
|
|
1739
|
+
gap: 2px;
|
|
1740
|
+
}
|
|
1741
|
+
.token-num {
|
|
1742
|
+
font-family: var(--font-mono);
|
|
1743
|
+
font-size: 13px;
|
|
1744
|
+
font-weight: 700;
|
|
1745
|
+
color: var(--text-main);
|
|
1746
|
+
}
|
|
1747
|
+
.token-lbl {
|
|
1748
|
+
font-size: 10.5px;
|
|
1749
|
+
color: var(--text-dim);
|
|
1750
|
+
}
|
|
1751
|
+
.btn-playground-case {
|
|
1752
|
+
background: rgba(56, 189, 248, 0.12);
|
|
1753
|
+
border: 1px solid rgba(56, 189, 248, 0.35);
|
|
1754
|
+
color: var(--accent-cyan);
|
|
1755
|
+
border-radius: 6px;
|
|
1756
|
+
padding: 5px 12px;
|
|
1757
|
+
font-size: 11.5px;
|
|
1758
|
+
font-weight: 600;
|
|
1759
|
+
cursor: pointer;
|
|
1760
|
+
transition: all 0.15s ease;
|
|
1761
|
+
touch-action: manipulation;
|
|
1762
|
+
}
|
|
1763
|
+
.btn-playground-case:hover {
|
|
1764
|
+
background: rgba(56, 189, 248, 0.22);
|
|
1765
|
+
}
|
|
1766
|
+
.diff-badge-reg {
|
|
1767
|
+
background: var(--accent-red-dim);
|
|
1768
|
+
color: var(--accent-red);
|
|
1769
|
+
border: 1px solid rgba(248, 113, 113, 0.3);
|
|
1770
|
+
padding: 2px 7px;
|
|
1771
|
+
border-radius: 4px;
|
|
1772
|
+
font-size: 10.5px;
|
|
1773
|
+
font-weight: 700;
|
|
1774
|
+
}
|
|
1775
|
+
.diff-badge-imp {
|
|
1776
|
+
background: var(--accent-emerald-dim);
|
|
1777
|
+
color: var(--accent-emerald);
|
|
1778
|
+
border: 1px solid rgba(52, 211, 153, 0.3);
|
|
1779
|
+
padding: 2px 7px;
|
|
1780
|
+
border-radius: 4px;
|
|
1781
|
+
font-size: 10.5px;
|
|
1782
|
+
font-weight: 700;
|
|
1783
|
+
}
|
|
1784
|
+
.diff-badge-neu {
|
|
1785
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1786
|
+
color: var(--text-dim);
|
|
1787
|
+
border: 1px solid var(--border-glass);
|
|
1788
|
+
padding: 2px 7px;
|
|
1789
|
+
border-radius: 4px;
|
|
1790
|
+
font-size: 10.5px;
|
|
1791
|
+
font-weight: 600;
|
|
1792
|
+
}
|
|
1793
|
+
.diff-kpi-grid {
|
|
1794
|
+
display: grid;
|
|
1795
|
+
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
|
1796
|
+
gap: 10px;
|
|
1797
|
+
}
|
|
1798
|
+
.diff-kpi-card {
|
|
1799
|
+
background: rgba(255, 255, 255, 0.03);
|
|
1800
|
+
border: 1px solid var(--border-glass);
|
|
1801
|
+
border-radius: var(--radius-sm);
|
|
1802
|
+
padding: 10px 12px;
|
|
1803
|
+
}
|
|
1804
|
+
</style>
|
|
1805
|
+
</head>
|
|
1806
|
+
<body>
|
|
1807
|
+
<header>
|
|
1808
|
+
<a href="/eval" class="brand">
|
|
1809
|
+
<div class="brand-icon">
|
|
1810
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
|
1811
|
+
<circle cx="12" cy="12" r="3"></circle>
|
|
1812
|
+
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"></path>
|
|
1813
|
+
</svg>
|
|
1814
|
+
</div>
|
|
1815
|
+
<div>
|
|
1816
|
+
<span class="brand-title">Kiku Eval Studio</span>
|
|
1817
|
+
<span class="brand-tag">Workbench</span>
|
|
1818
|
+
</div>
|
|
1819
|
+
</a>
|
|
1820
|
+
<div style="margin-left:auto; margin-right:12px;">
|
|
1821
|
+
<a href="/eval/dashboard" class="nav-pill" style="text-decoration:none; display:inline-flex; align-items:center; gap:6px;" title="Open Visual Benchmark Dashboard">
|
|
1822
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>
|
|
1823
|
+
<span>Dashboard</span>
|
|
1824
|
+
</a>
|
|
1825
|
+
</div>
|
|
1826
|
+
<div class="header-actions">
|
|
1827
|
+
<div class="status-pill">
|
|
1828
|
+
<span id="sseStatusDot" class="status-dot"></span>
|
|
1829
|
+
<span id="sseStatusText">Connecting</span>
|
|
1830
|
+
</div>
|
|
1831
|
+
<button id="btnAiSummary" class="btn" onclick="openAiSummaryModal()" title="AI Executive Benchmark Summary" style="background: linear-gradient(135deg, rgba(236, 72, 153, 0.22), rgba(168, 85, 247, 0.28)); border: 1px solid rgba(236, 72, 153, 0.5); color: #f472b6; font-weight: 600; display: inline-flex; align-items: center; gap: 6px; box-shadow: 0 0 12px rgba(236, 72, 153, 0.25);">
|
|
1832
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
|
1833
|
+
<span>AI Summary</span>
|
|
1834
|
+
</button>
|
|
1835
|
+
<button id="btnStart" class="btn btn-primary" onclick="startRun()">
|
|
1836
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
|
1837
|
+
<span>Run</span>
|
|
1838
|
+
</button>
|
|
1839
|
+
<button id="btnRetry" class="btn btn-warning" onclick="retryErrors()" title="Retry status error cases" style="display:none; align-items:center; gap:6px;">
|
|
1840
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6"></path><path d="M1 20v-6h6"></path><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
|
1841
|
+
<span id="btnRetryLabel">Retry</span>
|
|
1842
|
+
</button>
|
|
1843
|
+
<button id="btnStop" class="btn btn-danger" onclick="stopRun()" disabled>
|
|
1844
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><rect x="4" y="4" width="16" height="16" rx="2"></rect></svg>
|
|
1845
|
+
<span>Abort</span>
|
|
1846
|
+
</button>
|
|
1847
|
+
</div>
|
|
1848
|
+
</header>
|
|
1849
|
+
|
|
1850
|
+
<nav class="studio-subnav">
|
|
1851
|
+
<div class="subnav-left">
|
|
1852
|
+
<button id="subnavAiSummaryBtn" class="nav-pill" onclick="openAiSummaryModal()" style="background: rgba(236, 72, 153, 0.16); border-color: rgba(236, 72, 153, 0.4); color: #f472b6; font-weight: 600;" title="AI Executive Summary">
|
|
1853
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
|
1854
|
+
<span>AI Summary</span>
|
|
1855
|
+
</button>
|
|
1856
|
+
<button class="nav-pill" onclick="openCompareModal()">
|
|
1857
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>
|
|
1858
|
+
<span>Compare & Diff</span>
|
|
1859
|
+
</button>
|
|
1860
|
+
<button class="nav-pill" onclick="openPlaygroundModal()">
|
|
1861
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
|
|
1862
|
+
<span>Playground</span>
|
|
1863
|
+
</button>
|
|
1864
|
+
<button class="nav-pill" onclick="openSuitesModal()">
|
|
1865
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
|
|
1866
|
+
<span>Custom Suites</span>
|
|
1867
|
+
</button>
|
|
1868
|
+
<button class="nav-pill" onclick="openArenaModal()">
|
|
1869
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polygon points="12 8 8 12 12 16 16 12 12 8"></polygon></svg>
|
|
1870
|
+
<span>Model Arena</span>
|
|
1871
|
+
</button>
|
|
1872
|
+
<button class="nav-pill" onclick="openJudgeModal()">
|
|
1873
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></svg>
|
|
1874
|
+
<span>Judge Rubric</span>
|
|
1875
|
+
</button>
|
|
1876
|
+
</div>
|
|
1877
|
+
<div class="subnav-right">
|
|
1878
|
+
<div class="export-group">
|
|
1879
|
+
<button class="export-btn" onclick="loadLatestReport()" title="Pull latest data directly from artifacts/eval/live.json">
|
|
1880
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6"></path><path d="M1 20v-6h6"></path><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
|
1881
|
+
<span>Sync live.json</span>
|
|
1882
|
+
</button>
|
|
1883
|
+
<button class="export-btn" onclick="saveCurrentSnapshot()" title="Save current report as named snapshot">
|
|
1884
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>
|
|
1885
|
+
<span>Snapshot</span>
|
|
1886
|
+
</button>
|
|
1887
|
+
<button class="export-btn" onclick="exportReportPng()" title="Export benchmark infographic PNG">
|
|
1888
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
|
|
1889
|
+
<span>PNG</span>
|
|
1890
|
+
</button>
|
|
1891
|
+
<button class="export-btn" onclick="exportReportCsv()" title="Download CSV report">
|
|
1892
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
|
1893
|
+
<span>CSV</span>
|
|
1894
|
+
</button>
|
|
1895
|
+
<button class="export-btn" onclick="exportReportJson()" title="Download JSON report">
|
|
1896
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
|
|
1897
|
+
<span>JSON</span>
|
|
1898
|
+
</button>
|
|
1899
|
+
</div>
|
|
1900
|
+
</div>
|
|
1901
|
+
</nav>
|
|
1902
|
+
|
|
1903
|
+
<main>
|
|
1904
|
+
<div class="preset-chips">
|
|
1905
|
+
<span style="font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase;">Presets:</span>
|
|
1906
|
+
<button class="preset-chip active" onclick="applyPreset('single')">Single Case (1 ข้อ)</button>
|
|
1907
|
+
<button class="preset-chip" onclick="applyPreset('quick')">Quick Smoke (3)</button>
|
|
1908
|
+
<button class="preset-chip" onclick="applyPreset('catalog')">Catalog QA (${catalogCount})</button>
|
|
1909
|
+
${expandedCount > 0 ? `<button class="preset-chip" onclick="applyPreset('expanded')">Expanded (${expandedCount})</button>` : ''}
|
|
1910
|
+
<button class="preset-chip" onclick="applyPreset('security')">Security (${securityCount})</button>
|
|
1911
|
+
<button class="preset-chip" onclick="applyPreset('legacy')">Legacy Case IDs (${legacyCount})</button>
|
|
1912
|
+
<button class="preset-chip" onclick="applyPreset('all')">All Comprehensive (${totalCount})</button>
|
|
1913
|
+
</div>
|
|
1914
|
+
|
|
1915
|
+
<section class="config-card">
|
|
1916
|
+
<div class="config-header" onclick="toggleConfig()">
|
|
1917
|
+
<div class="config-title">
|
|
1918
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
1919
|
+
<span>Run Configuration</span>
|
|
1920
|
+
</div>
|
|
1921
|
+
<svg id="configChevron" class="config-chevron open" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
|
1922
|
+
</div>
|
|
1923
|
+
<div id="configBody" class="config-body">
|
|
1924
|
+
<div class="form-group">
|
|
1925
|
+
<label class="form-label" for="suiteSelect">Mode / Suite</label>
|
|
1926
|
+
<select id="suiteSelect" class="form-control" onchange="syncPresetChips(); saveConfigToStorage();">
|
|
1927
|
+
<option value="single" selected>Single Case (1 ข้อเฉพาะ)</option>
|
|
1928
|
+
<option value="quick">Quick Smoke (3 ข้อ)</option>
|
|
1929
|
+
<option value="catalog">Catalog QA (${catalogCount} ข้อ)</option>
|
|
1930
|
+
${expandedCount > 0 ? `<option value="expanded">New Cases (${expandedCount} ข้อ)</option>` : ''}
|
|
1931
|
+
<option value="security">Security (${securityCount} ข้อ)</option>
|
|
1932
|
+
<option value="legacy">Legacy Case IDs (${legacyCount} ข้อ)</option>
|
|
1933
|
+
<option value="all">All Comprehensive (${totalCount} ข้อ)</option>
|
|
1934
|
+
</select>
|
|
1935
|
+
</div>
|
|
1936
|
+
<div id="targetCaseGroup" class="form-group">
|
|
1937
|
+
<label class="form-label" for="caseSelect">Target Case (เลือกข้อที่ต้องการรันเดี่ยว)</label>
|
|
1938
|
+
<select id="caseSelect" class="form-control" onchange="saveConfigToStorage()">
|
|
1939
|
+
<option value="p01">p01 [profile] - ข้อมูลและสถิติพื้นฐาน จวีจิ้งอี</option>
|
|
1940
|
+
</select>
|
|
1941
|
+
</div>
|
|
1942
|
+
<div class="form-group">
|
|
1943
|
+
<label class="form-label" for="modelInput">Model ID</label>
|
|
1944
|
+
<input id="modelInput" type="text" class="form-control" value="google/gemma-4-e4b" spellcheck="false" oninput="saveConfigToStorage()">
|
|
1945
|
+
</div>
|
|
1946
|
+
<div class="form-group">
|
|
1947
|
+
<label class="form-label" for="baseUrlInput">Base URL</label>
|
|
1948
|
+
<input id="baseUrlInput" type="text" class="form-control" value="http://127.0.0.1:1234/v1" spellcheck="false" oninput="saveConfigToStorage()">
|
|
1949
|
+
</div>
|
|
1950
|
+
<div class="form-group">
|
|
1951
|
+
<label class="form-label" for="limitInput">Limit</label>
|
|
1952
|
+
<input id="limitInput" type="number" class="form-control" placeholder="1" value="1" min="1" max="${totalCount}" oninput="saveConfigToStorage()">
|
|
1953
|
+
</div>
|
|
1954
|
+
<div class="form-group">
|
|
1955
|
+
<label class="form-label" for="judgeModelInput">Judge Model</label>
|
|
1956
|
+
<input id="judgeModelInput" type="text" class="form-control" placeholder="Optional" spellcheck="false" oninput="saveConfigToStorage()">
|
|
1957
|
+
</div>
|
|
1958
|
+
<div class="form-group" style="justify-content: flex-end;">
|
|
1959
|
+
<label class="form-label" style="opacity: 0;">Resume</label>
|
|
1960
|
+
<label style="display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-main); cursor: pointer; min-height: 40px; padding: 0 4px; user-select: none;">
|
|
1961
|
+
<input id="resumeCheckbox" type="checkbox" onchange="saveConfigToStorage()" style="accent-color: var(--accent-rose); width: 16px; height: 16px; cursor: pointer;">
|
|
1962
|
+
<span style="font-weight: 500;">Resume (รันต่อจากเดิม)</span>
|
|
1963
|
+
</label>
|
|
1964
|
+
</div>
|
|
1965
|
+
</div>
|
|
1966
|
+
</section>
|
|
1967
|
+
|
|
1968
|
+
<section id="progressHero" class="progress-hero" style="display: none;">
|
|
1969
|
+
<div class="progress-top">
|
|
1970
|
+
<div class="progress-hero-title">
|
|
1971
|
+
<span id="runStatusBadge" class="badge-gate badge-neutral">IDLE</span>
|
|
1972
|
+
<span id="runTitle" class="progress-title-text">Evaluation running</span>
|
|
1973
|
+
</div>
|
|
1974
|
+
<div id="progressStats" class="progress-stats-text">0 / 0 cases (0%) • Elapsed: 0s</div>
|
|
1975
|
+
<div id="etaDisplay" style="display:none; font-size:12px; color:var(--accent,#a78bfa); margin-top:4px; font-weight:500; letter-spacing:0.01em;"></div>
|
|
1976
|
+
</div>
|
|
1977
|
+
<div class="progress-track">
|
|
1978
|
+
<div id="progressBarFill" class="progress-fill"></div>
|
|
1979
|
+
</div>
|
|
1980
|
+
<div id="activeCaseCard" class="active-case-card" style="display: none;">
|
|
1981
|
+
<div class="active-case-meta">
|
|
1982
|
+
<span id="activeCaseBadge" class="case-badge-pill">ACTIVE</span>
|
|
1983
|
+
<span id="activeCaseText" class="case-question-preview">Preparing case...</span>
|
|
1984
|
+
</div>
|
|
1985
|
+
<div id="activeToolLive" class="active-tool-live">
|
|
1986
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
|
|
1987
|
+
<span id="activeToolText">Processing</span>
|
|
1988
|
+
</div>
|
|
1989
|
+
</div>
|
|
1990
|
+
</section>
|
|
1991
|
+
|
|
1992
|
+
<section id="lmsStreamCard" class="lms-stream-card">
|
|
1993
|
+
<div class="lms-stream-header" onclick="toggleLmsPanel()">
|
|
1994
|
+
<div class="lms-header-left">
|
|
1995
|
+
<span class="lms-pulse-dot active" id="lmsStatusDot"></span>
|
|
1996
|
+
<span class="lms-title">LM Studio Live Reasoning & Stream</span>
|
|
1997
|
+
<span class="lms-live-badge" id="lmsLiveBadge">LIVE</span>
|
|
1998
|
+
<span class="lms-tag" id="lmsModelTag">google/gemma-4-e4b</span>
|
|
1999
|
+
</div>
|
|
2000
|
+
<div class="lms-header-right" onclick="event.stopPropagation()">
|
|
2001
|
+
<div class="lms-filters">
|
|
2002
|
+
<button class="lms-filter-btn active" data-lms-filter="all" onclick="setLmsFilter('all')">All</button>
|
|
2003
|
+
<button class="lms-filter-btn" data-lms-filter="thoughts" onclick="setLmsFilter('thoughts')">Thoughts</button>
|
|
2004
|
+
<button class="lms-filter-btn" data-lms-filter="tools" onclick="setLmsFilter('tools')">Tools</button>
|
|
2005
|
+
<button class="lms-filter-btn" data-lms-filter="json" onclick="setLmsFilter('json')">Raw</button>
|
|
2006
|
+
</div>
|
|
2007
|
+
<button class="lms-tool-btn" id="lmsAutoScrollBtn" onclick="toggleLmsAutoScroll()" title="Toggle Auto-scroll">Auto-scroll: ON</button>
|
|
2008
|
+
<button class="lms-tool-btn" onclick="clearLmsLogs()" title="Clear Terminal">Clear</button>
|
|
2009
|
+
<button class="lms-tool-btn" onclick="initLmsStream()" title="Reconnect Stream">Reconnect</button>
|
|
2010
|
+
<svg id="lmsChevron" class="config-chevron open" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
|
2011
|
+
</div>
|
|
2012
|
+
</div>
|
|
2013
|
+
<div id="lmsStreamBody" class="lms-stream-body">
|
|
2014
|
+
<div class="lms-telemetry-bar">
|
|
2015
|
+
<div class="lms-telemetry-item">
|
|
2016
|
+
<span class="lms-telemetry-label">Events</span>
|
|
2017
|
+
<span class="lms-telemetry-val" id="lmsEventCountVal">0</span>
|
|
2018
|
+
</div>
|
|
2019
|
+
<div class="lms-telemetry-item">
|
|
2020
|
+
<span class="lms-telemetry-label">TPS</span>
|
|
2021
|
+
<span class="lms-telemetry-val" id="lmsTpsVal">-</span>
|
|
2022
|
+
</div>
|
|
2023
|
+
<div class="lms-telemetry-item">
|
|
2024
|
+
<span class="lms-telemetry-label">TTFT</span>
|
|
2025
|
+
<span class="lms-telemetry-val" id="lmsTtftVal">-</span>
|
|
2026
|
+
</div>
|
|
2027
|
+
<div class="lms-telemetry-item">
|
|
2028
|
+
<span class="lms-telemetry-label">Predicted Tokens</span>
|
|
2029
|
+
<span class="lms-telemetry-val" id="lmsPredictedVal">-</span>
|
|
2030
|
+
</div>
|
|
2031
|
+
<div class="lms-telemetry-item">
|
|
2032
|
+
<span class="lms-telemetry-label">Prompt Tokens</span>
|
|
2033
|
+
<span class="lms-telemetry-val" id="lmsPromptVal">-</span>
|
|
2034
|
+
</div>
|
|
2035
|
+
<div class="lms-telemetry-item">
|
|
2036
|
+
<span class="lms-telemetry-label">Stop Reason</span>
|
|
2037
|
+
<span class="lms-telemetry-val" id="lmsStopReasonVal">-</span>
|
|
2038
|
+
</div>
|
|
2039
|
+
</div>
|
|
2040
|
+
<div id="lmsLogViewport" class="lms-log-viewport">
|
|
2041
|
+
<div class="lms-log-empty" id="lmsEmptyNotice">Listening for LM Studio inference events...</div>
|
|
2042
|
+
</div>
|
|
2043
|
+
</div>
|
|
2044
|
+
</section>
|
|
2045
|
+
|
|
2046
|
+
<section class="gate-banner">
|
|
2047
|
+
<div class="gate-title-group">
|
|
2048
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
2049
|
+
<span>ISO/IEC 42001 & NIST AI RMF Gate:</span>
|
|
2050
|
+
<span id="overallGateBadge" class="badge-gate badge-neutral">PENDING</span>
|
|
2051
|
+
</div>
|
|
2052
|
+
<div id="gateSubtext" style="font-size: 11px; color: var(--text-muted);">
|
|
2053
|
+
Awaiting evaluation run
|
|
2054
|
+
</div>
|
|
2055
|
+
<button class="nav-pill" onclick="openAiSummaryModal()" style="margin-left: auto; background: rgba(236, 72, 153, 0.15); border-color: rgba(236, 72, 153, 0.4); color: #f472b6; font-weight: 600;" title="Generate AI Benchmark Executive Summary">
|
|
2056
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
|
2057
|
+
<span>AI Summary</span>
|
|
2058
|
+
</button>
|
|
2059
|
+
</section>
|
|
2060
|
+
|
|
2061
|
+
<section class="kpi-grid">
|
|
2062
|
+
<div class="kpi-card">
|
|
2063
|
+
<div class="kpi-label-row">
|
|
2064
|
+
<span>Pass@1 Rate</span>
|
|
2065
|
+
<span class="kpi-target-tag">≥ 75%</span>
|
|
2066
|
+
</div>
|
|
2067
|
+
<div id="kpiPassAt1" class="kpi-metric-val" style="color: var(--accent-rose);">-</div>
|
|
2068
|
+
<div class="kpi-bottom-row">
|
|
2069
|
+
<span>Task Success</span>
|
|
2070
|
+
<span id="gatePassAt1" class="badge-gate badge-neutral">-</span>
|
|
2071
|
+
</div>
|
|
2072
|
+
</div>
|
|
2073
|
+
|
|
2074
|
+
<div class="kpi-card">
|
|
2075
|
+
<div class="kpi-label-row">
|
|
2076
|
+
<span>Tool F1-Score</span>
|
|
2077
|
+
<span class="kpi-target-tag">≥ 0.90</span>
|
|
2078
|
+
</div>
|
|
2079
|
+
<div id="kpiToolF1" class="kpi-metric-val" style="color: var(--accent-violet);">-</div>
|
|
2080
|
+
<div class="kpi-bottom-row">
|
|
2081
|
+
<span>Tool Precision</span>
|
|
2082
|
+
<span id="gateToolF1" class="badge-gate badge-neutral">-</span>
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
|
|
2086
|
+
<div class="kpi-card">
|
|
2087
|
+
<div class="kpi-label-row">
|
|
2088
|
+
<span>Groundedness</span>
|
|
2089
|
+
<span class="kpi-target-tag">≥ 75%</span>
|
|
2090
|
+
</div>
|
|
2091
|
+
<div id="kpiGroundedness" class="kpi-metric-val" style="color: var(--accent-cyan);">-</div>
|
|
2092
|
+
<div class="kpi-bottom-row">
|
|
2093
|
+
<span>Catalog Faith</span>
|
|
2094
|
+
<span id="gateGroundedness" class="badge-gate badge-neutral">-</span>
|
|
2095
|
+
</div>
|
|
2096
|
+
</div>
|
|
2097
|
+
|
|
2098
|
+
<div class="kpi-card">
|
|
2099
|
+
<div class="kpi-label-row">
|
|
2100
|
+
<span>Loop-Free</span>
|
|
2101
|
+
<span class="kpi-target-tag">≥ 95%</span>
|
|
2102
|
+
</div>
|
|
2103
|
+
<div id="kpiLoopFree" class="kpi-metric-val" style="color: var(--accent-emerald);">-</div>
|
|
2104
|
+
<div class="kpi-bottom-row">
|
|
2105
|
+
<span>Zero-Thrash</span>
|
|
2106
|
+
<span id="gateLoopFree" class="badge-gate badge-neutral">-</span>
|
|
2107
|
+
</div>
|
|
2108
|
+
</div>
|
|
2109
|
+
|
|
2110
|
+
<div class="kpi-card">
|
|
2111
|
+
<div class="kpi-label-row">
|
|
2112
|
+
<span>Hallucination</span>
|
|
2113
|
+
<span class="kpi-target-tag">≤ 5.0%</span>
|
|
2114
|
+
</div>
|
|
2115
|
+
<div id="kpiHallucination" class="kpi-metric-val" style="color: var(--accent-amber);">-</div>
|
|
2116
|
+
<div class="kpi-bottom-row">
|
|
2117
|
+
<span>Unverified</span>
|
|
2118
|
+
<span id="gateHallucination" class="badge-gate badge-neutral">-</span>
|
|
2119
|
+
</div>
|
|
2120
|
+
</div>
|
|
2121
|
+
|
|
2122
|
+
<div class="kpi-card">
|
|
2123
|
+
<div class="kpi-label-row">
|
|
2124
|
+
<span>Speed / TPS</span>
|
|
2125
|
+
<span class="kpi-target-tag">SLA</span>
|
|
2126
|
+
</div>
|
|
2127
|
+
<div id="kpiLatency" class="kpi-metric-val" style="color: #cbd5e1; font-size: 20px;">-</div>
|
|
2128
|
+
<div class="kpi-bottom-row">
|
|
2129
|
+
<span id="kpiTps">Decode: -</span>
|
|
2130
|
+
<span>p95: <span id="kpiP95">-</span></span>
|
|
2131
|
+
</div>
|
|
2132
|
+
</div>
|
|
2133
|
+
</section>
|
|
2134
|
+
|
|
2135
|
+
<section class="explorer-card">
|
|
2136
|
+
<div class="explorer-toolbar">
|
|
2137
|
+
<div class="filter-tabs">
|
|
2138
|
+
<button class="filter-tab active" data-filter="all" onclick="setFilter('all')">All (<span id="countAll">0</span>)</button>
|
|
2139
|
+
<button class="filter-tab" data-filter="completed" onclick="setFilter('completed')">Done (<span id="countCompleted">0</span>)</button>
|
|
2140
|
+
<button class="filter-tab" data-filter="anomalies" onclick="setFilter('anomalies')" style="color: #fbbf24;">Anomalies (<span id="countAnomalies">0</span>)</button>
|
|
2141
|
+
<button class="filter-tab" data-filter="error" onclick="setFilter('error')">Errors (<span id="countError">0</span>)</button>
|
|
2142
|
+
<button class="filter-tab" data-filter="tool-fail" onclick="setFilter('tool-fail')">Tool (<span id="countToolFail">0</span>)</button>
|
|
2143
|
+
<button class="filter-tab" data-filter="loop-fail" onclick="setFilter('loop-fail')">Loops (<span id="countLoopFail">0</span>)</button>
|
|
2144
|
+
<button class="filter-tab" data-filter="grounding-fail" onclick="setFilter('grounding-fail')">Grounding (<span id="countGroundingFail">0</span>)</button>
|
|
2145
|
+
</div>
|
|
2146
|
+
<div class="search-row">
|
|
2147
|
+
<input id="searchInput" type="text" class="search-input" placeholder="Search case ID, query..." oninput="handleSearch()">
|
|
2148
|
+
<button class="view-toggle-btn" onclick="toggleViewMode()" title="Toggle Card / Table View">
|
|
2149
|
+
<svg id="viewIcon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
|
|
2150
|
+
</button>
|
|
2151
|
+
</div>
|
|
2152
|
+
</div>
|
|
2153
|
+
|
|
2154
|
+
<div id="mobileCardsContainer" class="mobile-cards-container">
|
|
2155
|
+
<div style="text-align: center; color: var(--text-dim); padding: 36px 0; font-size: 13px;">
|
|
2156
|
+
No evaluation run loaded yet. Click <strong>Run</strong> to start.
|
|
2157
|
+
</div>
|
|
2158
|
+
</div>
|
|
2159
|
+
|
|
2160
|
+
<div id="tableViewContainer" class="table-view-container active-desktop">
|
|
2161
|
+
<table>
|
|
2162
|
+
<thead>
|
|
2163
|
+
<tr>
|
|
2164
|
+
<th>ID</th>
|
|
2165
|
+
<th>Category</th>
|
|
2166
|
+
<th>Question</th>
|
|
2167
|
+
<th>Status</th>
|
|
2168
|
+
<th>Coverage</th>
|
|
2169
|
+
<th>Tool Accuracy</th>
|
|
2170
|
+
<th>Loop Health</th>
|
|
2171
|
+
<th>Duration</th>
|
|
2172
|
+
<th>Action</th>
|
|
2173
|
+
</tr>
|
|
2174
|
+
</thead>
|
|
2175
|
+
<tbody id="casesTableBody">
|
|
2176
|
+
<tr>
|
|
2177
|
+
<td colspan="9" style="text-align: center; color: var(--text-dim); padding: 36px 0;">
|
|
2178
|
+
No evaluation run loaded yet.
|
|
2179
|
+
</td>
|
|
2180
|
+
</tr>
|
|
2181
|
+
</tbody>
|
|
2182
|
+
</table>
|
|
2183
|
+
</div>
|
|
2184
|
+
</section>
|
|
2185
|
+
</main>
|
|
2186
|
+
|
|
2187
|
+
<div id="drawerOverlay" class="drawer-overlay" onclick="closeDrawer()"></div>
|
|
2188
|
+
<aside id="detailDrawer" class="drawer">
|
|
2189
|
+
<div class="drawer-drag-handle"></div>
|
|
2190
|
+
<div class="drawer-header">
|
|
2191
|
+
<div class="drawer-title-area">
|
|
2192
|
+
<span id="drawerCaseId" class="pill pill-tag">ID</span>
|
|
2193
|
+
<span id="drawerTitle" class="drawer-title">Case Details</span>
|
|
2194
|
+
</div>
|
|
2195
|
+
<button class="btn-playground-case" style="margin-left: auto; margin-right: 8px;" onclick="openPlaygroundWithCurrentCase()">Debug in Playground</button>
|
|
2196
|
+
<button id="drawerRunBtn" class="btn-run-case" style="margin-right: 8px;" onclick="runSingleCaseById(document.getElementById('drawerCaseId').textContent)">Run Case</button>
|
|
2197
|
+
<button class="drawer-close" onclick="closeDrawer()">×</button>
|
|
2198
|
+
</div>
|
|
2199
|
+
<div id="drawerBody" class="drawer-body">
|
|
2200
|
+
</div>
|
|
2201
|
+
</aside>
|
|
2202
|
+
|
|
2203
|
+
<div id="compareModal" class="modal-overlay" onclick="if(event.target===this)closeCompareModal()">
|
|
2204
|
+
<div class="modal-window">
|
|
2205
|
+
<div class="modal-header">
|
|
2206
|
+
<div class="modal-title">
|
|
2207
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>
|
|
2208
|
+
<span>Run Comparison & Regression Diffing</span>
|
|
2209
|
+
</div>
|
|
2210
|
+
<button class="modal-close" onclick="closeCompareModal()">×</button>
|
|
2211
|
+
</div>
|
|
2212
|
+
<div class="modal-body">
|
|
2213
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr auto; gap: 10px; align-items: flex-end;">
|
|
2214
|
+
<div class="form-group" style="margin: 0;">
|
|
2215
|
+
<label class="form-label" for="compareBaseSelect">Baseline Snapshot</label>
|
|
2216
|
+
<select id="compareBaseSelect" class="form-control"></select>
|
|
2217
|
+
</div>
|
|
2218
|
+
<div class="form-group" style="margin: 0;">
|
|
2219
|
+
<label class="form-label" for="compareCandSelect">Candidate Snapshot</label>
|
|
2220
|
+
<select id="compareCandSelect" class="form-control"></select>
|
|
2221
|
+
</div>
|
|
2222
|
+
<button class="btn btn-primary" onclick="executeCompareDiff()">Run Diff</button>
|
|
2223
|
+
</div>
|
|
2224
|
+
<div id="compareSummaryRow" class="diff-kpi-grid" style="display: none;">
|
|
2225
|
+
<div class="diff-kpi-card">
|
|
2226
|
+
<div style="font-size: 11px; color: var(--text-dim);">Regressions</div>
|
|
2227
|
+
<div id="diffRegCount" style="font-size: 20px; font-weight: 700; color: var(--accent-red);">0</div>
|
|
2228
|
+
</div>
|
|
2229
|
+
<div class="diff-kpi-card">
|
|
2230
|
+
<div style="font-size: 11px; color: var(--text-dim);">Improvements</div>
|
|
2231
|
+
<div id="diffImpCount" style="font-size: 20px; font-weight: 700; color: var(--accent-emerald);">0</div>
|
|
2232
|
+
</div>
|
|
2233
|
+
<div class="diff-kpi-card">
|
|
2234
|
+
<div style="font-size: 11px; color: var(--text-dim);">Unchanged</div>
|
|
2235
|
+
<div id="diffUnchangedCount" style="font-size: 20px; font-weight: 700; color: var(--text-main);">0</div>
|
|
2236
|
+
</div>
|
|
2237
|
+
</div>
|
|
2238
|
+
<div id="compareDeltasContainer" style="display: none; flex-direction: column; gap: 12px;">
|
|
2239
|
+
<div class="detail-label">Metric Deltas</div>
|
|
2240
|
+
<div id="compareMetricTable" style="overflow-x: auto;"></div>
|
|
2241
|
+
<div class="detail-label">Regressions Breakdown</div>
|
|
2242
|
+
<div id="compareRegressionsList" style="max-height: 240px; overflow-y: auto;"></div>
|
|
2243
|
+
<div class="detail-label">Improvements Breakdown</div>
|
|
2244
|
+
<div id="compareImprovementsList" style="max-height: 240px; overflow-y: auto;"></div>
|
|
2245
|
+
</div>
|
|
2246
|
+
</div>
|
|
2247
|
+
</div>
|
|
2248
|
+
</div>
|
|
2249
|
+
|
|
2250
|
+
<div id="playgroundModal" class="modal-overlay" onclick="if(event.target===this)closePlaygroundModal()">
|
|
2251
|
+
<div class="modal-window">
|
|
2252
|
+
<div class="modal-header">
|
|
2253
|
+
<div class="modal-title">
|
|
2254
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
|
|
2255
|
+
<span>Interactive Case Debugger & Playground</span>
|
|
2256
|
+
</div>
|
|
2257
|
+
<button class="modal-close" onclick="closePlaygroundModal()">×</button>
|
|
2258
|
+
</div>
|
|
2259
|
+
<div class="modal-body">
|
|
2260
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
2261
|
+
<div class="form-group">
|
|
2262
|
+
<label class="form-label" for="pgCaseSelect">Case Template</label>
|
|
2263
|
+
<select id="pgCaseSelect" class="form-control" onchange="onPlaygroundCaseChange()"></select>
|
|
2264
|
+
</div>
|
|
2265
|
+
<div class="form-group">
|
|
2266
|
+
<label class="form-label" for="pgModelInput">Model ID</label>
|
|
2267
|
+
<input id="pgModelInput" type="text" class="form-control" value="google/gemma-4-e4b" spellcheck="false">
|
|
2268
|
+
</div>
|
|
2269
|
+
</div>
|
|
2270
|
+
<div class="form-group">
|
|
2271
|
+
<label class="form-label" for="pgQuestionInput">Question / Prompt (Editable)</label>
|
|
2272
|
+
<textarea id="pgQuestionInput" class="form-control" rows="3" placeholder="Enter custom question or modify template..." spellcheck="false"></textarea>
|
|
2273
|
+
</div>
|
|
2274
|
+
<div style="display: flex; gap: 10px; align-items: center;">
|
|
2275
|
+
<button id="btnRunPlayground" class="btn btn-primary" onclick="runPlaygroundDebug()">
|
|
2276
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
|
2277
|
+
<span>Run Live Debug</span>
|
|
2278
|
+
</button>
|
|
2279
|
+
<span id="pgStatusText" style="font-size: 11.5px; color: var(--text-dim);">Ready to test</span>
|
|
2280
|
+
</div>
|
|
2281
|
+
<div id="pgOutputSection" style="display: none; flex-direction: column; gap: 12px; margin-top: 8px;">
|
|
2282
|
+
<div class="detail-label">Agent Answer</div>
|
|
2283
|
+
<div id="pgAnswerBox" style="background: rgba(0,0,0,0.4); padding: 12px; border-radius: 6px; font-size: 13px; white-space: pre-wrap; max-height: 200px; overflow-y: auto;"></div>
|
|
2284
|
+
<div class="detail-label">Execution Trace & Tool Calls</div>
|
|
2285
|
+
<div id="pgTraceBox" style="max-height: 220px; overflow-y: auto;"></div>
|
|
2286
|
+
</div>
|
|
2287
|
+
</div>
|
|
2288
|
+
</div>
|
|
2289
|
+
</div>
|
|
2290
|
+
|
|
2291
|
+
<div id="suitesModal" class="modal-overlay" onclick="if(event.target===this)closeSuitesModal()">
|
|
2292
|
+
<div class="modal-window">
|
|
2293
|
+
<div class="modal-header">
|
|
2294
|
+
<div class="modal-title">
|
|
2295
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
|
|
2296
|
+
<span>Custom Case Curator & Suite Manager</span>
|
|
2297
|
+
</div>
|
|
2298
|
+
<button class="modal-close" onclick="closeSuitesModal()">×</button>
|
|
2299
|
+
</div>
|
|
2300
|
+
<div class="modal-body">
|
|
2301
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
2302
|
+
<div class="form-group">
|
|
2303
|
+
<label class="form-label" for="customSuitePreset">Saved Suites</label>
|
|
2304
|
+
<select id="customSuitePreset" class="form-control" onchange="loadSelectedSuitePreset()"></select>
|
|
2305
|
+
</div>
|
|
2306
|
+
<div class="form-group">
|
|
2307
|
+
<label class="form-label" for="customSuiteName">Suite Name</label>
|
|
2308
|
+
<input id="customSuiteName" type="text" class="form-control" placeholder="e.g. Thai Core QA">
|
|
2309
|
+
</div>
|
|
2310
|
+
</div>
|
|
2311
|
+
<div class="form-group">
|
|
2312
|
+
<label class="form-label" for="customSuiteDesc">Description</label>
|
|
2313
|
+
<input id="customSuiteDesc" type="text" class="form-control" placeholder="Brief description of this test suite">
|
|
2314
|
+
</div>
|
|
2315
|
+
<div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;">
|
|
2316
|
+
<span style="font-size: 11px; color: var(--text-dim);">Filter:</span>
|
|
2317
|
+
<button class="nav-pill" onclick="filterCuratorCategory('all')">All</button>
|
|
2318
|
+
<button class="nav-pill" onclick="filterCuratorCategory('profile')">Profile</button>
|
|
2319
|
+
<button class="nav-pill" onclick="filterCuratorCategory('works')">Works</button>
|
|
2320
|
+
<button class="nav-pill" onclick="filterCuratorCategory('music')">Music</button>
|
|
2321
|
+
<button class="nav-pill" onclick="filterCuratorCategory('security')">Security</button>
|
|
2322
|
+
<div style="margin-left: auto; display: flex; gap: 6px;">
|
|
2323
|
+
<button class="nav-pill" onclick="curatorSelectAll(true)">Select All</button>
|
|
2324
|
+
<button class="nav-pill" onclick="curatorSelectAll(false)">Clear</button>
|
|
2325
|
+
</div>
|
|
2326
|
+
</div>
|
|
2327
|
+
<div id="curatorCasesList" style="max-height: 280px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; border: 1px solid var(--border-glass); border-radius: 6px; padding: 6px;"></div>
|
|
2328
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
|
2329
|
+
<span id="curatorCountText" style="font-size: 12px; font-weight: 600; color: var(--accent-rose);">Selected: 0 cases</span>
|
|
2330
|
+
<div style="display: flex; gap: 8px;">
|
|
2331
|
+
<button class="btn btn-secondary" onclick="saveCustomSuite()">Save Suite</button>
|
|
2332
|
+
<button class="btn btn-primary" onclick="runCustomSuiteNow()">Run Suite Now</button>
|
|
2333
|
+
</div>
|
|
2334
|
+
</div>
|
|
2335
|
+
</div>
|
|
2336
|
+
</div>
|
|
2337
|
+
</div>
|
|
2338
|
+
|
|
2339
|
+
<div id="arenaModal" class="modal-overlay" onclick="if(event.target===this)closeArenaModal()">
|
|
2340
|
+
<div class="modal-window">
|
|
2341
|
+
<div class="modal-header">
|
|
2342
|
+
<div class="modal-title">
|
|
2343
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polygon points="12 8 8 12 12 16 16 12 12 8"></polygon></svg>
|
|
2344
|
+
<span>Multi-Model Arena & Matrix Benchmark</span>
|
|
2345
|
+
</div>
|
|
2346
|
+
<button class="modal-close" onclick="closeArenaModal()">×</button>
|
|
2347
|
+
</div>
|
|
2348
|
+
<div class="modal-body">
|
|
2349
|
+
<div style="font-size: 12px; color: var(--text-muted);">
|
|
2350
|
+
Comparative evaluation matrix across snapshot runs and model checkpoints under ISO/IEC 42001 & NIST AI RMF guidelines.
|
|
2351
|
+
</div>
|
|
2352
|
+
<div id="arenaTableWrap" style="overflow-x: auto;">
|
|
2353
|
+
<div style="text-align: center; color: var(--text-dim); padding: 24px;">Loading model matrix...</div>
|
|
2354
|
+
</div>
|
|
2355
|
+
</div>
|
|
2356
|
+
</div>
|
|
2357
|
+
</div>
|
|
2358
|
+
|
|
2359
|
+
<div id="judgeModal" class="modal-overlay" onclick="if(event.target===this)closeJudgeModal()">
|
|
2360
|
+
<div class="modal-window">
|
|
2361
|
+
<div class="modal-header">
|
|
2362
|
+
<div class="modal-title">
|
|
2363
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></svg>
|
|
2364
|
+
<span>LLM-as-a-Judge Rubric Studio</span>
|
|
2365
|
+
</div>
|
|
2366
|
+
<button class="modal-close" onclick="closeJudgeModal()">×</button>
|
|
2367
|
+
</div>
|
|
2368
|
+
<div class="modal-body">
|
|
2369
|
+
<div class="detail-label">ISO/IEC 42001 & NIST AI RMF Evaluation Rubric (Scale: 0 to 4)</div>
|
|
2370
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;">
|
|
2371
|
+
<div class="diff-kpi-card">
|
|
2372
|
+
<div style="font-weight: 700; color: var(--accent-rose); font-size: 12px;">Correctness (0-4)</div>
|
|
2373
|
+
<div style="font-size: 11px; color: var(--text-muted); margin-top: 4px;">Evaluates candidate against frozen ground truth reference and catalog evidence.</div>
|
|
2374
|
+
</div>
|
|
2375
|
+
<div class="diff-kpi-card">
|
|
2376
|
+
<div style="font-weight: 700; color: var(--accent-violet); font-size: 12px;">Grounding (0-4)</div>
|
|
2377
|
+
<div style="font-size: 11px; color: var(--text-muted); margin-top: 4px;">Every factual claim must be backed by observed tool results.</div>
|
|
2378
|
+
</div>
|
|
2379
|
+
<div class="diff-kpi-card">
|
|
2380
|
+
<div style="font-weight: 700; color: var(--accent-cyan); font-size: 12px;">Instruction Following (0-4)</div>
|
|
2381
|
+
<div style="font-size: 11px; color: var(--text-muted); margin-top: 4px;">Adherence to requested language (Thai, Chinese, English) and format.</div>
|
|
2382
|
+
</div>
|
|
2383
|
+
<div class="diff-kpi-card">
|
|
2384
|
+
<div style="font-weight: 700; color: var(--accent-amber); font-size: 12px;">Unsupported Claims</div>
|
|
2385
|
+
<div style="font-size: 11px; color: var(--text-muted); margin-top: 4px;">Flags hallucinated claims absent from verified catalog tools.</div>
|
|
2386
|
+
</div>
|
|
2387
|
+
</div>
|
|
2388
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr auto; gap: 10px; align-items: flex-end; margin-top: 8px;">
|
|
2389
|
+
<div class="form-group" style="margin: 0;">
|
|
2390
|
+
<label class="form-label" for="judgeModelStudioInput">Judge Model ID</label>
|
|
2391
|
+
<input id="judgeModelStudioInput" type="text" class="form-control" value="google/gemma-4-e4b" spellcheck="false">
|
|
2392
|
+
</div>
|
|
2393
|
+
<div class="form-group" style="margin: 0;">
|
|
2394
|
+
<label class="form-label" for="judgeBaseUrlStudioInput">Base URL</label>
|
|
2395
|
+
<input id="judgeBaseUrlStudioInput" type="text" class="form-control" value="http://127.0.0.1:1234/v1" spellcheck="false">
|
|
2396
|
+
</div>
|
|
2397
|
+
<button id="btnRunJudgeReport" class="btn btn-primary" onclick="runJudgeOnActiveReport()">Grade Report</button>
|
|
2398
|
+
</div>
|
|
2399
|
+
<div id="judgeProgressText" style="font-size: 11.5px; color: var(--text-dim);">Ready to grade.</div>
|
|
2400
|
+
</div>
|
|
2401
|
+
</div>
|
|
2402
|
+
</div>
|
|
2403
|
+
|
|
2404
|
+
<div id="studioAlertModal" class="modal-overlay" style="z-index: 150;" onclick="if(event.target===this)closeStudioAlert()">
|
|
2405
|
+
<div class="modal-window" style="max-width: 440px;">
|
|
2406
|
+
<div class="modal-header">
|
|
2407
|
+
<div class="modal-title" id="studioAlertHeaderTitle">
|
|
2408
|
+
<svg id="studioAlertIcon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--accent-cyan);"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
|
|
2409
|
+
<span id="studioAlertTitle">Notification</span>
|
|
2410
|
+
</div>
|
|
2411
|
+
<button class="modal-close" onclick="closeStudioAlert()">×</button>
|
|
2412
|
+
</div>
|
|
2413
|
+
<div class="modal-body" style="gap: 16px;">
|
|
2414
|
+
<div id="studioAlertMessage" style="font-size: 13px; line-height: 1.5; color: var(--text-main); word-break: break-word; white-space: pre-wrap;"></div>
|
|
2415
|
+
<div style="display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px;">
|
|
2416
|
+
<button id="studioAlertOkBtn" class="btn btn-primary" style="min-width: 80px;" onclick="closeStudioAlert()">OK</button>
|
|
2417
|
+
</div>
|
|
2418
|
+
</div>
|
|
2419
|
+
</div>
|
|
2420
|
+
</div>
|
|
2421
|
+
|
|
2422
|
+
<div id="studioPromptModal" class="modal-overlay" style="z-index: 150;" onclick="if(event.target===this)cancelStudioPrompt()">
|
|
2423
|
+
<div class="modal-window" style="max-width: 480px;">
|
|
2424
|
+
<div class="modal-header">
|
|
2425
|
+
<div class="modal-title">
|
|
2426
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--accent-primary);"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
|
2427
|
+
<span id="studioPromptTitle">Save Snapshot</span>
|
|
2428
|
+
</div>
|
|
2429
|
+
<button class="modal-close" onclick="cancelStudioPrompt()">×</button>
|
|
2430
|
+
</div>
|
|
2431
|
+
<div class="modal-body" style="gap: 14px;">
|
|
2432
|
+
<label id="studioPromptMessage" for="studioPromptInput" class="form-label" style="font-size: 13px; color: var(--text-main);">Enter snapshot name:</label>
|
|
2433
|
+
<input id="studioPromptInput" type="text" class="form-control" spellcheck="false" style="font-size: 13px; padding: 8px 12px;">
|
|
2434
|
+
<div style="display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px;">
|
|
2435
|
+
<button class="btn btn-ghost" style="min-width: 76px;" onclick="cancelStudioPrompt()">Cancel</button>
|
|
2436
|
+
<button id="studioPromptSubmitBtn" class="btn btn-primary" style="min-width: 80px;" onclick="submitStudioPrompt()">Confirm</button>
|
|
2437
|
+
</div>
|
|
2438
|
+
</div>
|
|
2439
|
+
</div>
|
|
2440
|
+
</div>
|
|
2441
|
+
|
|
2442
|
+
<div id="aiSummaryModal" class="modal-overlay" style="z-index: 140;" onclick="if(event.target===this)closeAiSummaryModal()">
|
|
2443
|
+
<div class="modal-window" style="max-width: 900px; max-height: 88vh; display: flex; flex-direction: column;">
|
|
2444
|
+
<div class="modal-header" style="border-bottom: 1px solid var(--border-glass); padding-bottom: 12px;">
|
|
2445
|
+
<div class="modal-title" style="display: flex; align-items: center; gap: 8px;">
|
|
2446
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--accent-rose);"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
|
2447
|
+
<span style="font-weight: 700; font-size: 16px;">AI Benchmark Executive Summary</span>
|
|
2448
|
+
<span class="pill pill-tag" id="aiSummaryModelBadge" style="font-size: 11px;">AI Executive Briefing</span>
|
|
2449
|
+
</div>
|
|
2450
|
+
<button class="modal-close" onclick="closeAiSummaryModal()">×</button>
|
|
2451
|
+
</div>
|
|
2452
|
+
<div class="modal-body" style="padding: 18px 24px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 14px;">
|
|
2453
|
+
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;">
|
|
2454
|
+
<div style="font-size: 12.5px; color: var(--text-muted);">
|
|
2455
|
+
High-level executive briefing synthesized from evaluation benchmarks. Pure direct synthesis without thinking blocks.
|
|
2456
|
+
</div>
|
|
2457
|
+
<div style="display: flex; gap: 8px;">
|
|
2458
|
+
<button id="btnAiSummaryRegen" class="btn btn-secondary" style="font-size: 12px; padding: 6px 12px;" onclick="generateAiSummary(true)">
|
|
2459
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6"></path><path d="M1 20v-6h6"></path><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
|
2460
|
+
<span>Regenerate</span>
|
|
2461
|
+
</button>
|
|
2462
|
+
<button id="btnAiSummaryCopy" class="btn btn-secondary" style="font-size: 12px; padding: 6px 12px;" onclick="copyAiSummary()">
|
|
2463
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
|
2464
|
+
<span>Copy</span>
|
|
2465
|
+
</button>
|
|
2466
|
+
</div>
|
|
2467
|
+
</div>
|
|
2468
|
+
<div id="aiSummaryLoading" style="display: none; text-align: center; padding: 48px 0; color: var(--text-muted);">
|
|
2469
|
+
<div class="status-dot running" style="width: 14px; height: 14px; margin: 0 auto 14px; display: inline-block;"></div>
|
|
2470
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-main);">Synthesizing Executive Benchmark Briefing...</div>
|
|
2471
|
+
<div style="font-size: 12px; color: var(--text-dim); margin-top: 4px;">Evaluating pass rates, SLA distributions, trajectory health, and anomalies</div>
|
|
2472
|
+
</div>
|
|
2473
|
+
<div id="aiSummaryContent" class="markdown-body" style="font-size: 13.5px; line-height: 1.65; color: var(--text-main); background: rgba(0,0,0,0.2); border-radius: var(--radius-md); padding: 18px 20px; border: 1px solid var(--border-glass);">
|
|
2474
|
+
<div style="color: var(--text-dim); font-style: italic;">No summary generated yet. Click "Generate" or open after running a benchmark.</div>
|
|
2475
|
+
</div>
|
|
2476
|
+
</div>
|
|
2477
|
+
<div class="modal-footer" style="padding: 12px 24px; border-top: 1px solid var(--border-glass); display: flex; justify-content: flex-end;">
|
|
2478
|
+
<button class="btn btn-primary" onclick="closeAiSummaryModal()">Close</button>
|
|
2479
|
+
</div>
|
|
2480
|
+
</div>
|
|
2481
|
+
</div>
|
|
2482
|
+
|
|
2483
|
+
<script>
|
|
2484
|
+
let currentReport = { rows: [] };
|
|
2485
|
+
let activeFilter = 'all';
|
|
2486
|
+
let searchQuery = '';
|
|
2487
|
+
let eventSource = null;
|
|
2488
|
+
let runStartTime = null;
|
|
2489
|
+
let runTimerInterval = null;
|
|
2490
|
+
let forceTableView = false;
|
|
2491
|
+
let activeDrawerRow = null;
|
|
2492
|
+
let isEvaluationRunning = false;
|
|
2493
|
+
|
|
2494
|
+
const CONFIG_STORAGE_KEY = 'kiku_eval_config_v1';
|
|
2495
|
+
const REPORT_STORAGE_KEY = 'kiku_eval_last_report_v1';
|
|
2496
|
+
|
|
2497
|
+
window.addEventListener('beforeunload', e => {
|
|
2498
|
+
if (isEvaluationRunning) {
|
|
2499
|
+
e.preventDefault();
|
|
2500
|
+
e.returnValue = '';
|
|
2501
|
+
return '';
|
|
2502
|
+
}
|
|
2503
|
+
});
|
|
2504
|
+
|
|
2505
|
+
function showSyncNotice(msg) {
|
|
2506
|
+
const toast = document.getElementById('syncToast');
|
|
2507
|
+
if (!toast) return;
|
|
2508
|
+
toast.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg> <span>' + escapeHtml(msg) + '</span>';
|
|
2509
|
+
toast.classList.add('show');
|
|
2510
|
+
setTimeout(() => {
|
|
2511
|
+
toast.classList.remove('show');
|
|
2512
|
+
}, 2400);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
function isErrorRow(r) {
|
|
2516
|
+
if (!r) return false;
|
|
2517
|
+
return r.status === 'error' || Boolean(r.error) || Boolean(r.timedOut);
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
function isAnomalyRow(r) {
|
|
2521
|
+
if (!r) return false;
|
|
2522
|
+
if (r.status === 'error' || r.error) return true;
|
|
2523
|
+
if (r.timedOut) return true;
|
|
2524
|
+
if (r.toolCorrectness && r.toolCorrectness.isCorrect === false) return true;
|
|
2525
|
+
if (r.trajectoryHealth?.hasLoop) return true;
|
|
2526
|
+
if (r.groundedness && r.groundedness.isGrounded === false) return true;
|
|
2527
|
+
if (r.coverage && r.coverage.allMatched === false) return true;
|
|
2528
|
+
if (r.judgeResult && r.judgeResult.verdict === 'FAIL') return true;
|
|
2529
|
+
if (r.passAt1 === false || (typeof r.passAt1 === 'number' && r.passAt1 === 0)) return true;
|
|
2530
|
+
if (r.durationMs && r.durationMs > 60000) return true;
|
|
2531
|
+
return false;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
function getAnomalyReason(r) {
|
|
2535
|
+
if (!r) return 'Unknown anomaly';
|
|
2536
|
+
if (r.error) return 'Error: ' + r.error;
|
|
2537
|
+
if (r.status === 'error') return 'Run Error';
|
|
2538
|
+
if (r.timedOut) return 'Timeout exceeded';
|
|
2539
|
+
if (r.trajectoryHealth?.hasLoop) return 'Infinite Re-planning Loop (' + r.trajectoryHealth.loopCount + ')';
|
|
2540
|
+
if (r.toolCorrectness && r.toolCorrectness.isCorrect === false) {
|
|
2541
|
+
const missing = r.toolCorrectness.missing?.length ? ('missing [' + r.toolCorrectness.missing.join(', ') + ']') : '';
|
|
2542
|
+
const unexp = r.toolCorrectness.unexpected?.length ? ('unexpected [' + r.toolCorrectness.unexpected.join(', ') + ']') : '';
|
|
2543
|
+
return 'Tool Mismatch: ' + [missing, unexp].filter(Boolean).join('; ');
|
|
2544
|
+
}
|
|
2545
|
+
if (r.coverage && r.coverage.allMatched === false) {
|
|
2546
|
+
return 'Coverage miss (' + (r.coverage.matched || 0) + '/' + (r.coverage.total || 0) + ')';
|
|
2547
|
+
}
|
|
2548
|
+
if (r.groundedness && r.groundedness.isGrounded === false) return 'Hallucination / Ungrounded';
|
|
2549
|
+
if (r.judgeResult && r.judgeResult.verdict === 'FAIL') return 'Judge verdict: FAIL';
|
|
2550
|
+
if (r.durationMs && r.durationMs > 60000) return 'High Latency (' + (r.durationMs / 1000).toFixed(1) + 's)';
|
|
2551
|
+
return 'Behavioral Anomaly';
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
async function retryErrors() {
|
|
2555
|
+
const rows = currentReport.rows || [];
|
|
2556
|
+
const errorRows = rows.filter(isErrorRow);
|
|
2557
|
+
if (!errorRows.length) {
|
|
2558
|
+
await showStudioAlert('No Errors Found', 'All evaluated cases executed without status errors.', 'success');
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
if (document.getElementById('btnStart').disabled) return;
|
|
2562
|
+
const model = document.getElementById('modelInput').value;
|
|
2563
|
+
const baseUrl = document.getElementById('baseUrlInput').value;
|
|
2564
|
+
const judgeModel = document.getElementById('judgeModelInput').value;
|
|
2565
|
+
|
|
2566
|
+
const errorIds = errorRows.map(r => r.id);
|
|
2567
|
+
const payload = {
|
|
2568
|
+
suite: 'all',
|
|
2569
|
+
ids: errorIds,
|
|
2570
|
+
retryErrors: true,
|
|
2571
|
+
model: model.trim() || undefined,
|
|
2572
|
+
baseUrl: baseUrl.trim() || undefined,
|
|
2573
|
+
judgeModel: judgeModel.trim() || undefined
|
|
2574
|
+
};
|
|
2575
|
+
|
|
2576
|
+
setRunningUiState(true);
|
|
2577
|
+
runStartTime = Date.now();
|
|
2578
|
+
if (runTimerInterval) clearInterval(runTimerInterval);
|
|
2579
|
+
runTimerInterval = setInterval(updateRunTimer, 1000);
|
|
2580
|
+
|
|
2581
|
+
try {
|
|
2582
|
+
const res = await fetch('/api/eval/start', {
|
|
2583
|
+
method: 'POST',
|
|
2584
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2585
|
+
body: JSON.stringify(payload)
|
|
2586
|
+
});
|
|
2587
|
+
if (!res.ok) {
|
|
2588
|
+
const err = await res.json();
|
|
2589
|
+
await showStudioAlert('Retry Failed', err.error || res.statusText, 'error');
|
|
2590
|
+
setRunningUiState(false);
|
|
2591
|
+
clearInterval(runTimerInterval);
|
|
2592
|
+
}
|
|
2593
|
+
} catch (err) {
|
|
2594
|
+
await showStudioAlert('Connection Error', 'Network error retrying status errors: ' + err.message, 'error');
|
|
2595
|
+
setRunningUiState(false);
|
|
2596
|
+
clearInterval(runTimerInterval);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
const retryAnomalies = retryErrors;
|
|
2600
|
+
|
|
2601
|
+
let cachedAiSummary = null;
|
|
2602
|
+
function openAiSummaryModal() {
|
|
2603
|
+
document.getElementById('aiSummaryModal').classList.add('open');
|
|
2604
|
+
if (!cachedAiSummary || (currentReport.rows && currentReport.rows.length > 0 && (!cachedAiSummary.caseCount || cachedAiSummary.caseCount !== currentReport.rows.length))) {
|
|
2605
|
+
generateAiSummary(false);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
function closeAiSummaryModal() {
|
|
2610
|
+
document.getElementById('aiSummaryModal').classList.remove('open');
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
function cleanThinkingBlocks(raw) {
|
|
2614
|
+
if (!raw) return '';
|
|
2615
|
+
let text = String(raw);
|
|
2616
|
+
text = text.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');
|
|
2617
|
+
text = text.replace(/<\\|channel>thought[\\s\\S]*?<channel\\|>/gi, '');
|
|
2618
|
+
text = text.replace(/<\\|channel>thought[\\s\\S]*?(?=\\n\\n|\\n[A-Z#*])/gi, '');
|
|
2619
|
+
text = text.replace(/^thought\\s*[\\r\\n][\\s\\S]*?(?=\\n\\n#|\\n\\n[A-Z])/gi, '');
|
|
2620
|
+
return text.trim();
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
function copyAiSummary() {
|
|
2624
|
+
if (!cachedAiSummary?.text) return;
|
|
2625
|
+
navigator.clipboard.writeText(cachedAiSummary.text).then(() => {
|
|
2626
|
+
showSyncNotice('AI Summary copied to clipboard');
|
|
2627
|
+
}).catch(() => {
|
|
2628
|
+
fallbackCopy(cachedAiSummary.text);
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
function renderMarkdownHtml(md) {
|
|
2633
|
+
if (!md) return '';
|
|
2634
|
+
var rawLines = String(md).split(String.fromCharCode(10));
|
|
2635
|
+
var out = [];
|
|
2636
|
+
var inTable = false;
|
|
2637
|
+
var tableHeaders = [];
|
|
2638
|
+
var tableRows = [];
|
|
2639
|
+
|
|
2640
|
+
function formatInline(s) {
|
|
2641
|
+
var str = escapeHtml(s);
|
|
2642
|
+
str = str.replace(/\\*\\*(.*?)\\*\\*/g, '<strong style="color: #fff; font-weight: 600;">$1</strong>');
|
|
2643
|
+
str = str.replace(/\\*(.*?)\\*/g, '<em>$1</em>');
|
|
2644
|
+
str = str.replace(new RegExp('\\\\x60([^\\\\x60]+)\\\\x60', 'g'), '<code style="background: rgba(255,255,255,0.08); color: var(--accent-cyan); padding: 2px 6px; border-radius: 4px; font-family: var(--font-mono); font-size: 12px;">$1</code>');
|
|
2645
|
+
return str;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
function flushTable() {
|
|
2649
|
+
if (!inTable) return;
|
|
2650
|
+
var tableHtml = '<div style="overflow-x: auto; margin: 16px 0; border: 1px solid var(--border-glass); border-radius: 8px; background: rgba(255,255,255,0.02);">';
|
|
2651
|
+
tableHtml += '<table style="width: 100%; border-collapse: collapse; font-size: 12.5px; text-align: left;">';
|
|
2652
|
+
tableHtml += '<thead><tr style="background: rgba(255,255,255,0.06); border-bottom: 1px solid var(--border-glass);">';
|
|
2653
|
+
tableHeaders.forEach(function(h) {
|
|
2654
|
+
tableHtml += '<th style="padding: 10px 14px; font-weight: 700; color: #fff; white-space: nowrap;">' + escapeHtml(h) + '</th>';
|
|
2655
|
+
});
|
|
2656
|
+
tableHtml += '</tr></thead><tbody>';
|
|
2657
|
+
tableRows.forEach(function(row, rIdx) {
|
|
2658
|
+
var bg = rIdx % 2 === 1 ? 'background: rgba(255,255,255,0.015);' : '';
|
|
2659
|
+
tableHtml += '<tr style="' + bg + ' border-bottom: 1px solid rgba(255,255,255,0.04);">';
|
|
2660
|
+
row.forEach(function(c, cIdx) {
|
|
2661
|
+
var cellContent = formatInline(c);
|
|
2662
|
+
if (c.indexOf('Excellent') !== -1) {
|
|
2663
|
+
cellContent = '<span style="display:inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; background: rgba(16,185,129,0.15); color: #34d399;">Excellent</span>';
|
|
2664
|
+
} else if (c.indexOf('Moderate') !== -1) {
|
|
2665
|
+
cellContent = '<span style="display:inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; background: rgba(56,189,248,0.15); color: #38bdf8;">Moderate</span>';
|
|
2666
|
+
} else if (c.indexOf('Needs Attention') !== -1) {
|
|
2667
|
+
cellContent = '<span style="display:inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; background: rgba(251,146,60,0.15); color: #fb923c;">Needs Attention</span>';
|
|
2668
|
+
}
|
|
2669
|
+
var align = (cIdx > 0 && cIdx < row.length - 1) ? 'text-align: center;' : '';
|
|
2670
|
+
tableHtml += '<td style="padding: 8px 14px; color: var(--text-main); ' + align + '">' + cellContent + '</td>';
|
|
2671
|
+
});
|
|
2672
|
+
tableHtml += '</tr>';
|
|
2673
|
+
});
|
|
2674
|
+
tableHtml += '</tbody></table></div>';
|
|
2675
|
+
out.push(tableHtml);
|
|
2676
|
+
inTable = false;
|
|
2677
|
+
tableHeaders = [];
|
|
2678
|
+
tableRows = [];
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
for (var i = 0; i < rawLines.length; i++) {
|
|
2682
|
+
var line = rawLines[i].trim();
|
|
2683
|
+
if (line.charAt(0) === '|' && line.charAt(line.length - 1) === '|') {
|
|
2684
|
+
var cells = line.slice(1, -1).split('|').map(function(s) { return s.trim(); });
|
|
2685
|
+
if (!inTable) {
|
|
2686
|
+
if (i + 1 < rawLines.length && rawLines[i + 1].trim().indexOf('---') !== -1) {
|
|
2687
|
+
inTable = true;
|
|
2688
|
+
tableHeaders = cells;
|
|
2689
|
+
tableRows = [];
|
|
2690
|
+
i++;
|
|
2691
|
+
continue;
|
|
2692
|
+
}
|
|
2693
|
+
} else {
|
|
2694
|
+
tableRows.push(cells);
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
if (inTable) {
|
|
2699
|
+
flushTable();
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
if (line.indexOf('### ') === 0) {
|
|
2703
|
+
out.push('<h3 style="font-size: 14.5px; font-weight: 700; color: var(--accent-rose); margin: 16px 0 6px 0;">' + escapeHtml(line.slice(4)) + '</h3>');
|
|
2704
|
+
} else if (line.indexOf('## ') === 0) {
|
|
2705
|
+
out.push('<h2 style="font-size: 16.5px; font-weight: 800; color: #fff; margin: 20px 0 10px 0; border-bottom: 1px solid var(--border-glass); padding-bottom: 6px;">' + escapeHtml(line.slice(3)) + '</h2>');
|
|
2706
|
+
} else if (line.indexOf('# ') === 0) {
|
|
2707
|
+
out.push('<h1 style="font-size: 19px; font-weight: 800; color: #fff; margin: 22px 0 12px 0;">' + escapeHtml(line.slice(2)) + '</h1>');
|
|
2708
|
+
} else if (/^\\d+\\.\\s+/.test(line)) {
|
|
2709
|
+
var numMatch = line.match(/^(\\d+)\\.\\s+(.*)$/);
|
|
2710
|
+
var formattedNum = numMatch ? ('<div style="margin-left: 18px; margin-bottom: 6px; color: var(--text-main);"><strong style="color: var(--accent-cyan);">' + numMatch[1] + '.</strong> ' + formatInline(numMatch[2]) + '</div>') : line;
|
|
2711
|
+
out.push(formattedNum);
|
|
2712
|
+
} else if (/^[-*]\\s+/.test(line)) {
|
|
2713
|
+
out.push('<li style="margin-left: 18px; margin-bottom: 4px; color: var(--text-main);">' + formatInline(line.replace(/^[-*]\\s+/, '')) + '</li>');
|
|
2714
|
+
} else if (line === '') {
|
|
2715
|
+
out.push('<div style="height: 10px;"></div>');
|
|
2716
|
+
} else {
|
|
2717
|
+
out.push('<div>' + formatInline(line) + '</div>');
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (inTable) flushTable();
|
|
2721
|
+
|
|
2722
|
+
return out.join('');
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
async function generateAiSummary(force = false) {
|
|
2726
|
+
const rows = currentReport.rows || [];
|
|
2727
|
+
if (!rows.length) {
|
|
2728
|
+
document.getElementById('aiSummaryContent').innerHTML = '<div style="color: var(--text-dim); text-align: center; padding: 24px;">No evaluation data available to summarize. Run a benchmark first.</div>';
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
const loadingEl = document.getElementById('aiSummaryLoading');
|
|
2732
|
+
const contentEl = document.getElementById('aiSummaryContent');
|
|
2733
|
+
const regenBtn = document.getElementById('btnAiSummaryRegen');
|
|
2734
|
+
loadingEl.style.display = 'block';
|
|
2735
|
+
contentEl.style.display = 'none';
|
|
2736
|
+
if (regenBtn) regenBtn.disabled = true;
|
|
2737
|
+
|
|
2738
|
+
try {
|
|
2739
|
+
const res = await fetch('/api/eval/ai-summary', {
|
|
2740
|
+
method: 'POST',
|
|
2741
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2742
|
+
body: JSON.stringify({
|
|
2743
|
+
report: currentReport,
|
|
2744
|
+
summary: currentReport.summary,
|
|
2745
|
+
model: document.getElementById('modelInput')?.value || undefined
|
|
2746
|
+
})
|
|
2747
|
+
});
|
|
2748
|
+
const data = await res.json();
|
|
2749
|
+
let summaryText = data.summary || data.text || '';
|
|
2750
|
+
summaryText = cleanThinkingBlocks(summaryText);
|
|
2751
|
+
cachedAiSummary = {
|
|
2752
|
+
text: summaryText,
|
|
2753
|
+
caseCount: rows.length,
|
|
2754
|
+
model: data.metadata?.model || data.model || 'Deterministic AI Engine',
|
|
2755
|
+
engine: data.metadata?.engine || 'unknown'
|
|
2756
|
+
};
|
|
2757
|
+
const badge = document.getElementById('aiSummaryModelBadge');
|
|
2758
|
+
if (badge) {
|
|
2759
|
+
if (data.metadata?.engine === 'ai') {
|
|
2760
|
+
badge.textContent = 'AI Model: ' + (data.metadata.model || 'LLM');
|
|
2761
|
+
badge.style.background = 'rgba(168, 85, 247, 0.2)';
|
|
2762
|
+
badge.style.color = 'var(--accent-purple)';
|
|
2763
|
+
badge.style.borderColor = 'rgba(168, 85, 247, 0.4)';
|
|
2764
|
+
} else {
|
|
2765
|
+
badge.textContent = 'Deterministic Analytics (Offline Fallback)';
|
|
2766
|
+
badge.style.background = 'rgba(245, 158, 11, 0.2)';
|
|
2767
|
+
badge.style.color = 'var(--accent-amber)';
|
|
2768
|
+
badge.style.borderColor = 'rgba(245, 158, 11, 0.4)';
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
contentEl.innerHTML = renderMarkdownHtml(summaryText);
|
|
2772
|
+
} catch (err) {
|
|
2773
|
+
contentEl.innerHTML = '<div style="color: var(--accent-red); padding: 18px;">Failed to generate AI summary: ' + escapeHtml(err.message) + '</div>';
|
|
2774
|
+
} finally {
|
|
2775
|
+
loadingEl.style.display = 'none';
|
|
2776
|
+
contentEl.style.display = 'block';
|
|
2777
|
+
if (regenBtn) regenBtn.disabled = false;
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
let studioAlertResolve = null;
|
|
2782
|
+
function showStudioAlert(title, message, type = 'info') {
|
|
2783
|
+
return new Promise(resolve => {
|
|
2784
|
+
studioAlertResolve = resolve;
|
|
2785
|
+
const modal = document.getElementById('studioAlertModal');
|
|
2786
|
+
const titleEl = document.getElementById('studioAlertTitle');
|
|
2787
|
+
const msgEl = document.getElementById('studioAlertMessage');
|
|
2788
|
+
const iconEl = document.getElementById('studioAlertIcon');
|
|
2789
|
+
const okBtn = document.getElementById('studioAlertOkBtn');
|
|
2790
|
+
if (titleEl) titleEl.textContent = title || 'Notice';
|
|
2791
|
+
if (msgEl) msgEl.textContent = message || '';
|
|
2792
|
+
if (iconEl) {
|
|
2793
|
+
if (type === 'error') {
|
|
2794
|
+
iconEl.style.color = 'var(--accent-red)';
|
|
2795
|
+
iconEl.innerHTML = '<circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>';
|
|
2796
|
+
} else if (type === 'success') {
|
|
2797
|
+
iconEl.style.color = 'var(--accent-emerald)';
|
|
2798
|
+
iconEl.innerHTML = '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>';
|
|
2799
|
+
} else {
|
|
2800
|
+
iconEl.style.color = 'var(--accent-cyan)';
|
|
2801
|
+
iconEl.innerHTML = '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line>';
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
if (modal) modal.classList.add('open');
|
|
2805
|
+
if (okBtn) setTimeout(() => okBtn.focus(), 50);
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
function closeStudioAlert() {
|
|
2810
|
+
const modal = document.getElementById('studioAlertModal');
|
|
2811
|
+
if (modal) modal.classList.remove('open');
|
|
2812
|
+
if (studioAlertResolve) {
|
|
2813
|
+
const res = studioAlertResolve;
|
|
2814
|
+
studioAlertResolve = null;
|
|
2815
|
+
res();
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
let studioPromptResolve = null;
|
|
2820
|
+
function showStudioPrompt(title, message, defaultValue = '', placeholder = '') {
|
|
2821
|
+
return new Promise(resolve => {
|
|
2822
|
+
studioPromptResolve = resolve;
|
|
2823
|
+
const modal = document.getElementById('studioPromptModal');
|
|
2824
|
+
const titleEl = document.getElementById('studioPromptTitle');
|
|
2825
|
+
const msgEl = document.getElementById('studioPromptMessage');
|
|
2826
|
+
const inputEl = document.getElementById('studioPromptInput');
|
|
2827
|
+
if (titleEl) titleEl.textContent = title || 'Prompt';
|
|
2828
|
+
if (msgEl) msgEl.textContent = message || '';
|
|
2829
|
+
if (inputEl) {
|
|
2830
|
+
inputEl.value = defaultValue || '';
|
|
2831
|
+
inputEl.placeholder = placeholder || '';
|
|
2832
|
+
}
|
|
2833
|
+
if (modal) modal.classList.add('open');
|
|
2834
|
+
if (inputEl) {
|
|
2835
|
+
setTimeout(() => {
|
|
2836
|
+
inputEl.focus();
|
|
2837
|
+
inputEl.select();
|
|
2838
|
+
}, 50);
|
|
2839
|
+
}
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
function submitStudioPrompt() {
|
|
2844
|
+
const modal = document.getElementById('studioPromptModal');
|
|
2845
|
+
const inputEl = document.getElementById('studioPromptInput');
|
|
2846
|
+
const val = inputEl ? inputEl.value.trim() : '';
|
|
2847
|
+
if (modal) modal.classList.remove('open');
|
|
2848
|
+
if (studioPromptResolve) {
|
|
2849
|
+
const res = studioPromptResolve;
|
|
2850
|
+
studioPromptResolve = null;
|
|
2851
|
+
res(val);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
function cancelStudioPrompt() {
|
|
2856
|
+
const modal = document.getElementById('studioPromptModal');
|
|
2857
|
+
if (modal) modal.classList.remove('open');
|
|
2858
|
+
if (studioPromptResolve) {
|
|
2859
|
+
const res = studioPromptResolve;
|
|
2860
|
+
studioPromptResolve = null;
|
|
2861
|
+
res(null);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
window.addEventListener('keydown', e => {
|
|
2866
|
+
const promptModal = document.getElementById('studioPromptModal');
|
|
2867
|
+
if (promptModal && promptModal.classList.contains('open')) {
|
|
2868
|
+
if (e.key === 'Enter') {
|
|
2869
|
+
e.preventDefault();
|
|
2870
|
+
submitStudioPrompt();
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
2873
|
+
if (e.key === 'Escape') {
|
|
2874
|
+
e.preventDefault();
|
|
2875
|
+
cancelStudioPrompt();
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
const alertModal = document.getElementById('studioAlertModal');
|
|
2880
|
+
if (alertModal && alertModal.classList.contains('open')) {
|
|
2881
|
+
if (e.key === 'Enter' || e.key === 'Escape') {
|
|
2882
|
+
e.preventDefault();
|
|
2883
|
+
closeStudioAlert();
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
if (e.key === 'Escape') {
|
|
2888
|
+
closeCompareModal();
|
|
2889
|
+
closePlaygroundModal();
|
|
2890
|
+
closeSuitesModal();
|
|
2891
|
+
closeArenaModal();
|
|
2892
|
+
closeJudgeModal();
|
|
2893
|
+
closeDrawer();
|
|
2894
|
+
}
|
|
2895
|
+
});
|
|
2896
|
+
|
|
2897
|
+
async function saveCurrentSnapshot() {
|
|
2898
|
+
const defaultName = 'run_' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
2899
|
+
const name = await showStudioPrompt('Save Snapshot', 'Enter snapshot name:', defaultName, 'Snapshot name...');
|
|
2900
|
+
if (!name) return;
|
|
2901
|
+
try {
|
|
2902
|
+
const res = await fetch('/api/eval/snapshot', {
|
|
2903
|
+
method: 'POST',
|
|
2904
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2905
|
+
body: JSON.stringify({ name })
|
|
2906
|
+
});
|
|
2907
|
+
if (res.ok) {
|
|
2908
|
+
showSyncNotice('Snapshot saved: ' + name);
|
|
2909
|
+
} else {
|
|
2910
|
+
const err = await res.json();
|
|
2911
|
+
await showStudioAlert('Save Snapshot Failed', err.error || 'Server error', 'error');
|
|
2912
|
+
}
|
|
2913
|
+
} catch (err) {
|
|
2914
|
+
await showStudioAlert('Save Snapshot Failed', err.message, 'error');
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
function triggerDownload(url, ext) {
|
|
2919
|
+
const uuid = (typeof crypto !== 'undefined' && crypto.randomUUID) ? crypto.randomUUID() : (Math.random().toString(36).slice(2) + '-' + Date.now());
|
|
2920
|
+
const filename = uuid + '.' + ext;
|
|
2921
|
+
const a = document.createElement('a');
|
|
2922
|
+
a.href = url;
|
|
2923
|
+
a.download = filename;
|
|
2924
|
+
document.body.appendChild(a);
|
|
2925
|
+
a.click();
|
|
2926
|
+
document.body.removeChild(a);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
function exportReportPng() {
|
|
2930
|
+
showSyncNotice('Downloading Benchmark PNG...');
|
|
2931
|
+
triggerDownload('/api/eval/export/png', 'png');
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
function exportReportCsv() {
|
|
2935
|
+
showSyncNotice('Downloading CSV report...');
|
|
2936
|
+
triggerDownload('/api/eval/export/csv', 'csv');
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
function exportReportJson() {
|
|
2940
|
+
showSyncNotice('Downloading JSON report...');
|
|
2941
|
+
triggerDownload('/api/eval/export/json', 'json');
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
function openCompareModal() {
|
|
2945
|
+
document.getElementById('compareModal').classList.add('open');
|
|
2946
|
+
loadCompareSnapshots();
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
function closeCompareModal() {
|
|
2950
|
+
document.getElementById('compareModal').classList.remove('open');
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
async function loadCompareSnapshots() {
|
|
2954
|
+
const bSel = document.getElementById('compareBaseSelect');
|
|
2955
|
+
const cSel = document.getElementById('compareCandSelect');
|
|
2956
|
+
try {
|
|
2957
|
+
const res = await fetch('/api/eval/history');
|
|
2958
|
+
const list = res.ok ? await res.json() : ['live'];
|
|
2959
|
+
const options = list.map(item => '<option value="' + item + '">' + item + '</option>').join('');
|
|
2960
|
+
bSel.innerHTML = options;
|
|
2961
|
+
cSel.innerHTML = options;
|
|
2962
|
+
if (list.length > 1) {
|
|
2963
|
+
cSel.selectedIndex = 1;
|
|
2964
|
+
}
|
|
2965
|
+
} catch {}
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
async function executeCompareDiff() {
|
|
2969
|
+
const base = document.getElementById('compareBaseSelect').value;
|
|
2970
|
+
const cand = document.getElementById('compareCandSelect').value;
|
|
2971
|
+
const summaryRow = document.getElementById('compareSummaryRow');
|
|
2972
|
+
const deltasContainer = document.getElementById('compareDeltasContainer');
|
|
2973
|
+
try {
|
|
2974
|
+
const res = await fetch('/api/eval/compare?baseline=' + encodeURIComponent(base) + '&candidate=' + encodeURIComponent(cand));
|
|
2975
|
+
if (!res.ok) {
|
|
2976
|
+
const err = await res.json();
|
|
2977
|
+
await showStudioAlert('Compare Error', err.error || 'Failed to compare', 'error');
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
const data = await res.json();
|
|
2981
|
+
summaryRow.style.display = 'grid';
|
|
2982
|
+
deltasContainer.style.display = 'flex';
|
|
2983
|
+
document.getElementById('diffRegCount').textContent = data.caseDiffs?.summary?.regressionsCount ?? 0;
|
|
2984
|
+
document.getElementById('diffImpCount').textContent = data.caseDiffs?.summary?.improvementsCount ?? 0;
|
|
2985
|
+
document.getElementById('diffUnchangedCount').textContent = data.caseDiffs?.summary?.unchangedCount ?? 0;
|
|
2986
|
+
|
|
2987
|
+
let tableHtml = '<table style="width: 100%; border-collapse: collapse; font-size: 11.5px;">' +
|
|
2988
|
+
'<thead><tr style="border-bottom: 1px solid var(--border-glass); color: var(--text-dim); text-align: left;">' +
|
|
2989
|
+
'<th style="padding: 6px;">Metric</th><th style="padding: 6px;">Baseline</th><th style="padding: 6px;">Candidate</th><th style="padding: 6px;">Delta</th>' +
|
|
2990
|
+
'</tr></thead><tbody>';
|
|
2991
|
+
for (const [m, d] of Object.entries(data.metricDeltas || {})) {
|
|
2992
|
+
const delta = d.delta;
|
|
2993
|
+
let deltaClass = 'diff-badge-neu';
|
|
2994
|
+
let sign = '';
|
|
2995
|
+
if (delta !== null && delta !== undefined) {
|
|
2996
|
+
if (delta > 0) { deltaClass = 'diff-badge-imp'; sign = '+'; }
|
|
2997
|
+
else if (delta < 0) { deltaClass = 'diff-badge-reg'; }
|
|
2998
|
+
}
|
|
2999
|
+
tableHtml += '<tr style="border-bottom: 1px solid rgba(255,255,255,0.04);">' +
|
|
3000
|
+
'<td style="padding: 6px; font-weight: 500;">' + escapeHtml(m) + '</td>' +
|
|
3001
|
+
'<td style="padding: 6px; color: var(--text-muted);">' + (d.baseline ?? '-') + '</td>' +
|
|
3002
|
+
'<td style="padding: 6px; color: var(--text-main); font-weight: 600;">' + (d.candidate ?? '-') + '</td>' +
|
|
3003
|
+
'<td style="padding: 6px;"><span class="' + deltaClass + '">' + (delta !== null ? (sign + delta) : '-') + '</span></td>' +
|
|
3004
|
+
'</tr>';
|
|
3005
|
+
}
|
|
3006
|
+
tableHtml += '</tbody></table>';
|
|
3007
|
+
document.getElementById('compareMetricTable').innerHTML = tableHtml;
|
|
3008
|
+
|
|
3009
|
+
const regList = data.caseDiffs?.regressions || [];
|
|
3010
|
+
if (regList.length === 0) {
|
|
3011
|
+
document.getElementById('compareRegressionsList').innerHTML = '<div style="color: var(--accent-emerald); font-size: 12px; padding: 12px; background: rgba(52,211,153,0.06); border-radius: 6px;">Zero regressions detected between snapshots.</div>';
|
|
3012
|
+
} else {
|
|
3013
|
+
document.getElementById('compareRegressionsList').innerHTML = regList.map(r =>
|
|
3014
|
+
'<div style="background: rgba(248,113,113,0.06); border: 1px solid rgba(248,113,113,0.2); border-radius: 6px; padding: 8px 12px; font-size: 12px; margin-bottom: 6px;">' +
|
|
3015
|
+
'<div style="display: flex; justify-content: space-between; font-weight: 600;">' +
|
|
3016
|
+
'<span style="color: var(--accent-red);">' + r.id + ' [' + escapeHtml(r.category) + ']</span>' +
|
|
3017
|
+
'<span class="diff-badge-reg">Regression</span>' +
|
|
3018
|
+
'</div>' +
|
|
3019
|
+
'<div style="color: var(--text-dim); margin-top: 4px; font-size: 11px;">Baseline: status=' + r.baseline.status + ', allMatched=' + r.baseline.allMatched + ', tool=' + r.baseline.toolCorrect + ' → Candidate: status=' + r.candidate.status + ', allMatched=' + r.candidate.allMatched + ', tool=' + r.candidate.toolCorrect + '</div>' +
|
|
3020
|
+
'</div>'
|
|
3021
|
+
).join('');
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
const impList = data.caseDiffs?.improvements || [];
|
|
3025
|
+
if (impList.length === 0) {
|
|
3026
|
+
document.getElementById('compareImprovementsList').innerHTML = '<div style="color: var(--text-dim); font-size: 12px; padding: 12px;">No new case improvements in this diff.</div>';
|
|
3027
|
+
} else {
|
|
3028
|
+
document.getElementById('compareImprovementsList').innerHTML = impList.map(i =>
|
|
3029
|
+
'<div style="background: rgba(52,211,153,0.06); border: 1px solid rgba(52,211,153,0.2); border-radius: 6px; padding: 8px 12px; font-size: 12px; margin-bottom: 6px;">' +
|
|
3030
|
+
'<div style="display: flex; justify-content: space-between; font-weight: 600;">' +
|
|
3031
|
+
'<span style="color: var(--accent-emerald);">' + i.id + ' [' + escapeHtml(i.category) + ']</span>' +
|
|
3032
|
+
'<span class="diff-badge-imp">Improvement</span>' +
|
|
3033
|
+
'</div>' +
|
|
3034
|
+
'<div style="color: var(--text-dim); margin-top: 4px; font-size: 11px;">Baseline: status=' + i.baseline.status + ' → Candidate: status=' + i.candidate.status + ', allMatched=' + i.candidate.allMatched + '</div>' +
|
|
3035
|
+
'</div>'
|
|
3036
|
+
).join('');
|
|
3037
|
+
}
|
|
3038
|
+
} catch (err) {
|
|
3039
|
+
await showStudioAlert('Compare Error', 'Failed to execute diff: ' + err.message, 'error');
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
function openPlaygroundModal() {
|
|
3044
|
+
document.getElementById('playgroundModal').classList.add('open');
|
|
3045
|
+
populatePlaygroundCases();
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function closePlaygroundModal() {
|
|
3049
|
+
document.getElementById('playgroundModal').classList.remove('open');
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
function populatePlaygroundCases() {
|
|
3053
|
+
const sel = document.getElementById('pgCaseSelect');
|
|
3054
|
+
if (!sel || !window.allAvailableCases?.length) return;
|
|
3055
|
+
sel.innerHTML = window.allAvailableCases.map(c =>
|
|
3056
|
+
'<option value="' + c.id + '">' + c.id + ' [' + c.category + '] ' + escapeHtml(c.question).slice(0, 50) + '...</option>'
|
|
3057
|
+
).join('');
|
|
3058
|
+
if (!document.getElementById('pgQuestionInput').value) {
|
|
3059
|
+
onPlaygroundCaseChange();
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
function onPlaygroundCaseChange() {
|
|
3064
|
+
const caseId = document.getElementById('pgCaseSelect').value;
|
|
3065
|
+
const found = (window.allAvailableCases || []).find(c => c.id === caseId);
|
|
3066
|
+
if (found) {
|
|
3067
|
+
document.getElementById('pgQuestionInput').value = found.question || '';
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
function openPlaygroundWithCurrentCase() {
|
|
3072
|
+
if (!activeDrawerRow) return;
|
|
3073
|
+
openPlaygroundModal();
|
|
3074
|
+
const sel = document.getElementById('pgCaseSelect');
|
|
3075
|
+
if (sel) sel.value = activeDrawerRow.id;
|
|
3076
|
+
document.getElementById('pgQuestionInput').value = activeDrawerRow.question || '';
|
|
3077
|
+
const mInput = document.getElementById('modelInput');
|
|
3078
|
+
if (mInput) document.getElementById('pgModelInput').value = mInput.value;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
async function runPlaygroundDebug() {
|
|
3082
|
+
const caseId = document.getElementById('pgCaseSelect').value;
|
|
3083
|
+
const customQuestion = document.getElementById('pgQuestionInput').value;
|
|
3084
|
+
const model = document.getElementById('pgModelInput').value;
|
|
3085
|
+
const bInput = document.getElementById('baseUrlInput');
|
|
3086
|
+
const baseUrl = bInput ? bInput.value : 'http://127.0.0.1:1234/v1';
|
|
3087
|
+
const btn = document.getElementById('btnRunPlayground');
|
|
3088
|
+
const statusText = document.getElementById('pgStatusText');
|
|
3089
|
+
const outSec = document.getElementById('pgOutputSection');
|
|
3090
|
+
|
|
3091
|
+
btn.disabled = true;
|
|
3092
|
+
statusText.textContent = 'Agent executing case...';
|
|
3093
|
+
outSec.style.display = 'none';
|
|
3094
|
+
|
|
3095
|
+
try {
|
|
3096
|
+
const res = await fetch('/api/eval/playground', {
|
|
3097
|
+
method: 'POST',
|
|
3098
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3099
|
+
body: JSON.stringify({ caseId, customQuestion, model, baseUrl })
|
|
3100
|
+
});
|
|
3101
|
+
const data = await res.json();
|
|
3102
|
+
if (!res.ok || !data.ok) {
|
|
3103
|
+
statusText.textContent = 'Execution failed: ' + (data.error || 'Server error');
|
|
3104
|
+
btn.disabled = false;
|
|
3105
|
+
return;
|
|
3106
|
+
}
|
|
3107
|
+
statusText.textContent = 'Completed in ' + (data.result?.durationMs || 0) + 'ms';
|
|
3108
|
+
outSec.style.display = 'flex';
|
|
3109
|
+
document.getElementById('pgAnswerBox').textContent = data.result?.answer || data.result?.error || 'No answer';
|
|
3110
|
+
|
|
3111
|
+
const trace = data.result?.trace || [];
|
|
3112
|
+
if (trace.length === 0) {
|
|
3113
|
+
document.getElementById('pgTraceBox').innerHTML = '<div style="color: var(--text-dim); font-size: 12px;">No tool trace events recorded.</div>';
|
|
3114
|
+
} else {
|
|
3115
|
+
document.getElementById('pgTraceBox').innerHTML = '<div class="react-timeline">' + trace.map(t => {
|
|
3116
|
+
const isResult = t.event === 'result';
|
|
3117
|
+
const title = isResult ? ('Observation: ' + t.name + ' (' + (t.durationMs || 0) + 'ms)') : ('Action: ' + t.name);
|
|
3118
|
+
const payload = isResult ? t.value : t.parameters;
|
|
3119
|
+
const jsonStr = payload ? JSON.stringify(payload, null, 2) : '';
|
|
3120
|
+
return '<div class="timeline-step">' +
|
|
3121
|
+
'<div style="font-weight: 600; font-size: 12px; color: ' + (isResult ? 'var(--accent-emerald)' : 'var(--accent-cyan)') + ';">' + escapeHtml(title) + '</div>' +
|
|
3122
|
+
(jsonStr ? '<pre class="code-block">' + escapeHtml(jsonStr) + '</pre>' : '') +
|
|
3123
|
+
'</div>';
|
|
3124
|
+
}).join('') + '</div>';
|
|
3125
|
+
}
|
|
3126
|
+
} catch (err) {
|
|
3127
|
+
statusText.textContent = 'Error: ' + err.message;
|
|
3128
|
+
} finally {
|
|
3129
|
+
btn.disabled = false;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
window.customSuitesList = [];
|
|
3134
|
+
window.curatorSelectedSet = new Set();
|
|
3135
|
+
window.activeCuratorCategory = 'all';
|
|
3136
|
+
|
|
3137
|
+
function openSuitesModal() {
|
|
3138
|
+
document.getElementById('suitesModal').classList.add('open');
|
|
3139
|
+
loadCustomSuites();
|
|
3140
|
+
renderCuratorCases('all');
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
function closeSuitesModal() {
|
|
3144
|
+
document.getElementById('suitesModal').classList.remove('open');
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
async function loadCustomSuites() {
|
|
3148
|
+
try {
|
|
3149
|
+
const res = await fetch('/api/eval/custom-suites');
|
|
3150
|
+
if (res.ok) {
|
|
3151
|
+
window.customSuitesList = await res.json();
|
|
3152
|
+
const sel = document.getElementById('customSuitePreset');
|
|
3153
|
+
sel.innerHTML = '<option value="">-- Choose Preset or Author New --</option>' +
|
|
3154
|
+
window.customSuitesList.map(s => '<option value="' + s.id + '">' + escapeHtml(s.name) + ' (' + s.caseIds.length + ' cases)</option>').join('');
|
|
3155
|
+
}
|
|
3156
|
+
} catch {}
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
function loadSelectedSuitePreset() {
|
|
3160
|
+
const sId = document.getElementById('customSuitePreset').value;
|
|
3161
|
+
const found = (window.customSuitesList || []).find(s => s.id === sId);
|
|
3162
|
+
if (!found) return;
|
|
3163
|
+
document.getElementById('customSuiteName').value = found.name || '';
|
|
3164
|
+
document.getElementById('customSuiteDesc').value = found.description || '';
|
|
3165
|
+
window.curatorSelectedSet = new Set(found.caseIds || []);
|
|
3166
|
+
renderCuratorCases(window.activeCuratorCategory || 'all');
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
function filterCuratorCategory(cat) {
|
|
3170
|
+
renderCuratorCases(cat);
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
function renderCuratorCases(cat) {
|
|
3174
|
+
window.activeCuratorCategory = cat;
|
|
3175
|
+
const list = window.allAvailableCases || [];
|
|
3176
|
+
const filtered = cat === 'all' ? list : list.filter(c => c.category === cat);
|
|
3177
|
+
const container = document.getElementById('curatorCasesList');
|
|
3178
|
+
container.innerHTML = filtered.map(c => {
|
|
3179
|
+
const isChecked = window.curatorSelectedSet.has(c.id);
|
|
3180
|
+
return '<label style="display: flex; align-items: center; gap: 8px; font-size: 11.5px; padding: 4px 6px; cursor: pointer; border-radius: 4px;">' +
|
|
3181
|
+
'<input type="checkbox" class="curator-chk" value="' + c.id + '" ' + (isChecked ? 'checked' : '') + ' onchange="onCuratorCheckboxChange(this)">' +
|
|
3182
|
+
'<span style="font-weight: 600; color: var(--accent-rose); width: 44px;">' + c.id + '</span>' +
|
|
3183
|
+
'<span style="color: var(--text-dim); width: 68px;">[' + c.category + ']</span>' +
|
|
3184
|
+
'<span style="color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">' + escapeHtml(c.question) + '</span>' +
|
|
3185
|
+
'</label>';
|
|
3186
|
+
}).join('');
|
|
3187
|
+
updateCuratorCount();
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
function onCuratorCheckboxChange(el) {
|
|
3191
|
+
if (!window.curatorSelectedSet) window.curatorSelectedSet = new Set();
|
|
3192
|
+
if (el.checked) {
|
|
3193
|
+
window.curatorSelectedSet.add(el.value);
|
|
3194
|
+
} else {
|
|
3195
|
+
window.curatorSelectedSet.delete(el.value);
|
|
3196
|
+
}
|
|
3197
|
+
updateCuratorCount();
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
function curatorSelectAll(bool) {
|
|
3201
|
+
if (!window.curatorSelectedSet) window.curatorSelectedSet = new Set();
|
|
3202
|
+
const list = window.allAvailableCases || [];
|
|
3203
|
+
const filtered = window.activeCuratorCategory === 'all' ? list : list.filter(c => c.category === window.activeCuratorCategory);
|
|
3204
|
+
filtered.forEach(c => {
|
|
3205
|
+
if (bool) {
|
|
3206
|
+
window.curatorSelectedSet.add(c.id);
|
|
3207
|
+
} else {
|
|
3208
|
+
window.curatorSelectedSet.delete(c.id);
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
document.querySelectorAll('.curator-chk').forEach(chk => {
|
|
3212
|
+
chk.checked = bool;
|
|
3213
|
+
});
|
|
3214
|
+
updateCuratorCount();
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
function updateCuratorCount() {
|
|
3218
|
+
const count = window.curatorSelectedSet ? window.curatorSelectedSet.size : 0;
|
|
3219
|
+
document.getElementById('curatorCountText').textContent = 'Selected: ' + count + ' cases';
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
async function saveCustomSuite() {
|
|
3223
|
+
const name = document.getElementById('customSuiteName').value.trim();
|
|
3224
|
+
const description = document.getElementById('customSuiteDesc').value.trim();
|
|
3225
|
+
if (!name) { await showStudioAlert('Input Required', 'Please enter a suite name.'); return; }
|
|
3226
|
+
const caseIds = Array.from(window.curatorSelectedSet || []);
|
|
3227
|
+
if (caseIds.length === 0) { await showStudioAlert('Selection Required', 'Please select at least 1 case.'); return; }
|
|
3228
|
+
|
|
3229
|
+
try {
|
|
3230
|
+
const res = await fetch('/api/eval/custom-suites', {
|
|
3231
|
+
method: 'POST',
|
|
3232
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3233
|
+
body: JSON.stringify({ name, description, caseIds })
|
|
3234
|
+
});
|
|
3235
|
+
if (res.ok) {
|
|
3236
|
+
showSyncNotice('Custom suite saved');
|
|
3237
|
+
loadCustomSuites();
|
|
3238
|
+
}
|
|
3239
|
+
} catch (err) {
|
|
3240
|
+
await showStudioAlert('Save Suite Failed', err.message, 'error');
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
async function runCustomSuiteNow() {
|
|
3245
|
+
const caseIds = Array.from(window.curatorSelectedSet || []);
|
|
3246
|
+
if (caseIds.length === 0) { await showStudioAlert('Selection Required', 'Please select at least 1 case.'); return; }
|
|
3247
|
+
closeSuitesModal();
|
|
3248
|
+
document.getElementById('suiteSelect').value = 'single';
|
|
3249
|
+
const caseSel = document.getElementById('caseSelect');
|
|
3250
|
+
if (caseSel && caseIds[0]) caseSel.value = caseIds[0];
|
|
3251
|
+
window.customSuiteRunIds = caseIds.join(',');
|
|
3252
|
+
startRun();
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
function openArenaModal() {
|
|
3256
|
+
document.getElementById('arenaModal').classList.add('open');
|
|
3257
|
+
loadModelArena();
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
function closeArenaModal() {
|
|
3261
|
+
document.getElementById('arenaModal').classList.remove('open');
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
async function loadModelArena() {
|
|
3265
|
+
const wrap = document.getElementById('arenaTableWrap');
|
|
3266
|
+
try {
|
|
3267
|
+
const res = await fetch('/api/eval/matrix');
|
|
3268
|
+
const list = res.ok ? await res.json() : [];
|
|
3269
|
+
if (list.length === 0) {
|
|
3270
|
+
wrap.innerHTML = '<div style="text-align: center; color: var(--text-dim); padding: 24px;">No evaluated snapshot models found in artifacts/eval.</div>';
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 8px;">' +
|
|
3274
|
+
'<thead><tr style="border-bottom: 1px solid var(--border-glass); color: var(--text-dim); text-align: left;">' +
|
|
3275
|
+
'<th style="padding: 8px;">Model / Snapshot</th>' +
|
|
3276
|
+
'<th style="padding: 8px;">Pass@1</th>' +
|
|
3277
|
+
'<th style="padding: 8px;">Tool F1</th>' +
|
|
3278
|
+
'<th style="padding: 8px;">Grounded</th>' +
|
|
3279
|
+
'<th style="padding: 8px;">Loop-Free</th>' +
|
|
3280
|
+
'<th style="padding: 8px;">Median Latency</th>' +
|
|
3281
|
+
'<th style="padding: 8px;">Cases</th>' +
|
|
3282
|
+
'</tr></thead><tbody>';
|
|
3283
|
+
for (const item of list) {
|
|
3284
|
+
const s = item.summary || {};
|
|
3285
|
+
const pass = Math.round((s.passAt1 ?? s.lexicalCoverageRate ?? 0) * 100);
|
|
3286
|
+
const tool = Math.round((s.toolF1 ?? s.toolAccuracyRate ?? 0) * 100);
|
|
3287
|
+
const ground = Math.round((s.groundednessRate ?? 0) * 100);
|
|
3288
|
+
const loop = Math.round((s.loopFreeRate ?? 1) * 100);
|
|
3289
|
+
const med = s.medianMs ? (s.medianMs + 'ms') : '-';
|
|
3290
|
+
html += '<tr style="border-bottom: 1px solid rgba(255,255,255,0.04);">' +
|
|
3291
|
+
'<td style="padding: 8px; font-weight: 600; color: var(--text-main);">' + escapeHtml(item.name) + '<div style="font-size: 10px; color: var(--text-dim); font-weight: normal;">' + escapeHtml(item.model) + '</div></td>' +
|
|
3292
|
+
'<td style="padding: 8px; color: var(--accent-rose); font-weight: 700;">' + pass + '%</td>' +
|
|
3293
|
+
'<td style="padding: 8px; color: var(--accent-violet);">' + tool + '%</td>' +
|
|
3294
|
+
'<td style="padding: 8px; color: var(--accent-cyan);">' + ground + '%</td>' +
|
|
3295
|
+
'<td style="padding: 8px; color: var(--accent-emerald);">' + loop + '%</td>' +
|
|
3296
|
+
'<td style="padding: 8px; color: var(--text-muted);">' + med + '</td>' +
|
|
3297
|
+
'<td style="padding: 8px; color: var(--text-dim);">' + (item.caseCount || 0) + '</td>' +
|
|
3298
|
+
'</tr>';
|
|
3299
|
+
}
|
|
3300
|
+
html += '</tbody></table>';
|
|
3301
|
+
wrap.innerHTML = html;
|
|
3302
|
+
} catch (err) {
|
|
3303
|
+
wrap.innerHTML = '<div style="color: var(--accent-red); padding: 20px;">Failed to load arena: ' + err.message + '</div>';
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
function openJudgeModal() {
|
|
3308
|
+
document.getElementById('judgeModal').classList.add('open');
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
function closeJudgeModal() {
|
|
3312
|
+
document.getElementById('judgeModal').classList.remove('open');
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
async function runJudgeOnActiveReport() {
|
|
3316
|
+
const judgeModel = document.getElementById('judgeModelStudioInput').value.trim();
|
|
3317
|
+
const baseUrl = document.getElementById('judgeBaseUrlStudioInput').value.trim();
|
|
3318
|
+
const btn = document.getElementById('btnRunJudgeReport');
|
|
3319
|
+
const progress = document.getElementById('judgeProgressText');
|
|
3320
|
+
|
|
3321
|
+
btn.disabled = true;
|
|
3322
|
+
progress.textContent = 'LLM Judge evaluating cases...';
|
|
3323
|
+
|
|
3324
|
+
try {
|
|
3325
|
+
const res = await fetch('/api/eval/judge', {
|
|
3326
|
+
method: 'POST',
|
|
3327
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3328
|
+
body: JSON.stringify({ judgeModel, baseUrl })
|
|
3329
|
+
});
|
|
3330
|
+
const data = await res.json();
|
|
3331
|
+
if (!res.ok) {
|
|
3332
|
+
progress.textContent = 'Judge error: ' + (data.error || 'Evaluation failed');
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
progress.textContent = 'Judging complete! Inspect verdicts in Case Details drawer.';
|
|
3336
|
+
showSyncNotice('Report graded with Judge');
|
|
3337
|
+
if (data.report) {
|
|
3338
|
+
currentReport = data.report;
|
|
3339
|
+
renderDashboard();
|
|
3340
|
+
}
|
|
3341
|
+
} catch (err) {
|
|
3342
|
+
progress.textContent = 'Error: ' + err.message;
|
|
3343
|
+
} finally {
|
|
3344
|
+
btn.disabled = false;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
function saveConfigToStorage() {
|
|
3349
|
+
try {
|
|
3350
|
+
const cfg = {
|
|
3351
|
+
suite: document.getElementById('suiteSelect')?.value,
|
|
3352
|
+
targetCase: document.getElementById('caseSelect')?.value,
|
|
3353
|
+
model: document.getElementById('modelInput')?.value,
|
|
3354
|
+
baseUrl: document.getElementById('baseUrlInput')?.value,
|
|
3355
|
+
limit: document.getElementById('limitInput')?.value,
|
|
3356
|
+
judgeModel: document.getElementById('judgeModelInput')?.value,
|
|
3357
|
+
resume: document.getElementById('resumeCheckbox')?.checked
|
|
3358
|
+
};
|
|
3359
|
+
localStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(cfg));
|
|
3360
|
+
} catch {}
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
function restoreConfigFromStorage() {
|
|
3364
|
+
try {
|
|
3365
|
+
const raw = localStorage.getItem(CONFIG_STORAGE_KEY);
|
|
3366
|
+
if (!raw) return;
|
|
3367
|
+
const cfg = JSON.parse(raw);
|
|
3368
|
+
if (cfg.suite) {
|
|
3369
|
+
const suiteSel = document.getElementById('suiteSelect');
|
|
3370
|
+
if (suiteSel) {
|
|
3371
|
+
suiteSel.value = cfg.suite;
|
|
3372
|
+
syncPresetChips();
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
if (cfg.targetCase && document.getElementById('caseSelect')) {
|
|
3376
|
+
document.getElementById('caseSelect').value = cfg.targetCase;
|
|
3377
|
+
}
|
|
3378
|
+
if (cfg.model !== undefined && document.getElementById('modelInput')) {
|
|
3379
|
+
document.getElementById('modelInput').value = cfg.model;
|
|
3380
|
+
}
|
|
3381
|
+
if (cfg.baseUrl !== undefined && document.getElementById('baseUrlInput')) {
|
|
3382
|
+
document.getElementById('baseUrlInput').value = cfg.baseUrl;
|
|
3383
|
+
}
|
|
3384
|
+
if (cfg.limit !== undefined && document.getElementById('limitInput')) {
|
|
3385
|
+
document.getElementById('limitInput').value = cfg.limit;
|
|
3386
|
+
}
|
|
3387
|
+
if (cfg.judgeModel !== undefined && document.getElementById('judgeModelInput')) {
|
|
3388
|
+
document.getElementById('judgeModelInput').value = cfg.judgeModel;
|
|
3389
|
+
}
|
|
3390
|
+
if (cfg.resume !== undefined && document.getElementById('resumeCheckbox')) {
|
|
3391
|
+
document.getElementById('resumeCheckbox').checked = Boolean(cfg.resume);
|
|
3392
|
+
}
|
|
3393
|
+
} catch {}
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
function saveReportToStorage(rep) {
|
|
3397
|
+
try {
|
|
3398
|
+
if (rep && Array.isArray(rep.rows) && rep.rows.length > 0) {
|
|
3399
|
+
localStorage.setItem(REPORT_STORAGE_KEY, JSON.stringify(rep));
|
|
3400
|
+
}
|
|
3401
|
+
} catch {}
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function restoreReportFromStorage() {
|
|
3405
|
+
try {
|
|
3406
|
+
const raw = localStorage.getItem(REPORT_STORAGE_KEY);
|
|
3407
|
+
if (raw) {
|
|
3408
|
+
const cached = JSON.parse(raw);
|
|
3409
|
+
if (Array.isArray(cached.rows) && cached.rows.length > 0 && (!currentReport.rows || !currentReport.rows.length)) {
|
|
3410
|
+
currentReport = cached;
|
|
3411
|
+
renderDashboard();
|
|
3412
|
+
showSyncNotice('Restored local draft');
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
} catch {}
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
function copyActiveDrawerField(field) {
|
|
3419
|
+
if (!activeDrawerRow) return;
|
|
3420
|
+
const val = field === 'question' ? (activeDrawerRow.question || '') : (activeDrawerRow.answer || activeDrawerRow.error || '');
|
|
3421
|
+
copyText(val);
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
function toggleConfig() {
|
|
3425
|
+
const body = document.getElementById('configBody');
|
|
3426
|
+
const chev = document.getElementById('configChevron');
|
|
3427
|
+
const isHidden = body.style.display === 'none';
|
|
3428
|
+
body.style.display = isHidden ? 'grid' : 'none';
|
|
3429
|
+
chev.classList.toggle('open', isHidden);
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
function applyPreset(name) {
|
|
3433
|
+
document.querySelectorAll('.preset-chip').forEach(c => c.classList.remove('active'));
|
|
3434
|
+
const found = Array.from(document.querySelectorAll('.preset-chip')).find(c => c.textContent.toLowerCase().includes(name));
|
|
3435
|
+
if (found) found.classList.add('active');
|
|
3436
|
+
|
|
3437
|
+
const suiteSelect = document.getElementById('suiteSelect');
|
|
3438
|
+
const limitInput = document.getElementById('limitInput');
|
|
3439
|
+
const targetCaseGroup = document.getElementById('targetCaseGroup');
|
|
3440
|
+
|
|
3441
|
+
if (name === 'single') {
|
|
3442
|
+
suiteSelect.value = 'single';
|
|
3443
|
+
limitInput.value = '1';
|
|
3444
|
+
if (targetCaseGroup) targetCaseGroup.style.display = 'flex';
|
|
3445
|
+
} else if (name === 'quick') {
|
|
3446
|
+
suiteSelect.value = 'quick';
|
|
3447
|
+
limitInput.value = '3';
|
|
3448
|
+
if (targetCaseGroup) targetCaseGroup.style.display = 'none';
|
|
3449
|
+
} else {
|
|
3450
|
+
suiteSelect.value = name;
|
|
3451
|
+
limitInput.value = '';
|
|
3452
|
+
if (targetCaseGroup) targetCaseGroup.style.display = 'none';
|
|
3453
|
+
}
|
|
3454
|
+
saveConfigToStorage();
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
function syncPresetChips() {
|
|
3458
|
+
const val = document.getElementById('suiteSelect').value;
|
|
3459
|
+
document.querySelectorAll('.preset-chip').forEach(c => {
|
|
3460
|
+
c.classList.toggle('active', c.textContent.toLowerCase().includes(val));
|
|
3461
|
+
});
|
|
3462
|
+
const targetCaseGroup = document.getElementById('targetCaseGroup');
|
|
3463
|
+
if (targetCaseGroup) {
|
|
3464
|
+
targetCaseGroup.style.display = (val === 'single') ? 'flex' : 'none';
|
|
3465
|
+
}
|
|
3466
|
+
const limitInput = document.getElementById('limitInput');
|
|
3467
|
+
if (val === 'single') {
|
|
3468
|
+
limitInput.value = '1';
|
|
3469
|
+
} else if (val === 'quick') {
|
|
3470
|
+
limitInput.value = '3';
|
|
3471
|
+
} else if (limitInput.value === '1' || limitInput.value === '3') {
|
|
3472
|
+
limitInput.value = '';
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
function toggleViewMode() {
|
|
3477
|
+
forceTableView = !forceTableView;
|
|
3478
|
+
const mCards = document.getElementById('mobileCardsContainer');
|
|
3479
|
+
const tView = document.getElementById('tableViewContainer');
|
|
3480
|
+
const isCardsVisible = window.getComputedStyle(mCards).display !== 'none';
|
|
3481
|
+
if (isCardsVisible) {
|
|
3482
|
+
mCards.style.setProperty('display', 'none', 'important');
|
|
3483
|
+
tView.style.setProperty('display', 'block', 'important');
|
|
3484
|
+
} else {
|
|
3485
|
+
mCards.style.setProperty('display', 'flex', 'important');
|
|
3486
|
+
tView.style.setProperty('display', 'none', 'important');
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
let lmsEventSource = null;
|
|
3491
|
+
let lmsFilter = 'all';
|
|
3492
|
+
let lmsAutoScroll = true;
|
|
3493
|
+
let lmsEventCount = 0;
|
|
3494
|
+
const MAX_LMS_LOGS = 250;
|
|
3495
|
+
|
|
3496
|
+
function initLmsStream() {
|
|
3497
|
+
if (lmsEventSource) {
|
|
3498
|
+
try { lmsEventSource.close(); } catch {}
|
|
3499
|
+
}
|
|
3500
|
+
const dot = document.getElementById('lmsStatusDot');
|
|
3501
|
+
const badge = document.getElementById('lmsLiveBadge');
|
|
3502
|
+
if (badge) {
|
|
3503
|
+
badge.textContent = 'CONNECTING';
|
|
3504
|
+
badge.style.color = 'var(--accent-amber)';
|
|
3505
|
+
badge.style.borderColor = 'rgba(251, 191, 36, 0.4)';
|
|
3506
|
+
}
|
|
3507
|
+
const streamUrl = (window.location.protocol === 'http:' || window.location.protocol === 'https:')
|
|
3508
|
+
? '/api/eval/lms/stream'
|
|
3509
|
+
: 'http://localhost:3001/api/eval/lms/stream';
|
|
3510
|
+
lmsEventSource = new EventSource(streamUrl);
|
|
3511
|
+
lmsEventSource.onopen = () => {
|
|
3512
|
+
if (dot) dot.className = 'lms-pulse-dot active';
|
|
3513
|
+
if (badge) {
|
|
3514
|
+
badge.textContent = 'LIVE';
|
|
3515
|
+
badge.style.color = 'var(--accent-emerald)';
|
|
3516
|
+
badge.style.borderColor = 'rgba(52, 211, 153, 0.35)';
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3519
|
+
lmsEventSource.onerror = () => {
|
|
3520
|
+
if (dot) dot.className = 'lms-pulse-dot offline';
|
|
3521
|
+
if (badge) {
|
|
3522
|
+
badge.textContent = 'OFFLINE';
|
|
3523
|
+
badge.style.color = 'var(--accent-red)';
|
|
3524
|
+
badge.style.borderColor = 'rgba(239, 68, 68, 0.4)';
|
|
3525
|
+
}
|
|
3526
|
+
};
|
|
3527
|
+
lmsEventSource.addEventListener('lms_log', (e) => {
|
|
3528
|
+
try {
|
|
3529
|
+
const payload = JSON.parse(e.data);
|
|
3530
|
+
lmsEventCount++;
|
|
3531
|
+
const cEl = document.getElementById('lmsEventCountVal');
|
|
3532
|
+
if (cEl) cEl.textContent = String(lmsEventCount);
|
|
3533
|
+
handleLmsLogPayload(payload);
|
|
3534
|
+
} catch {}
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
function handleLmsLogPayload(item) {
|
|
3539
|
+
const emptyNotice = document.getElementById('lmsEmptyNotice');
|
|
3540
|
+
if (emptyNotice) emptyNotice.style.display = 'none';
|
|
3541
|
+
|
|
3542
|
+
const data = item.data || item;
|
|
3543
|
+
if (data.modelIdentifier && document.getElementById('lmsModelTag')) {
|
|
3544
|
+
document.getElementById('lmsModelTag').textContent = data.modelIdentifier;
|
|
3545
|
+
}
|
|
3546
|
+
if (data.stats) {
|
|
3547
|
+
if (data.stats.tokensPerSecond != null && document.getElementById('lmsTpsVal')) {
|
|
3548
|
+
document.getElementById('lmsTpsVal').textContent = Number(data.stats.tokensPerSecond).toFixed(1) + ' tok/s';
|
|
3549
|
+
}
|
|
3550
|
+
if (data.stats.timeToFirstTokenSec != null && document.getElementById('lmsTtftVal')) {
|
|
3551
|
+
document.getElementById('lmsTtftVal').textContent = Number(data.stats.timeToFirstTokenSec).toFixed(2) + 's';
|
|
3552
|
+
}
|
|
3553
|
+
if (data.stats.predictedTokensCount != null && document.getElementById('lmsPredictedVal')) {
|
|
3554
|
+
document.getElementById('lmsPredictedVal').textContent = data.stats.predictedTokensCount;
|
|
3555
|
+
}
|
|
3556
|
+
if (data.stats.promptTokensCount != null && document.getElementById('lmsPromptVal')) {
|
|
3557
|
+
document.getElementById('lmsPromptVal').textContent = data.stats.promptTokensCount;
|
|
3558
|
+
}
|
|
3559
|
+
if (data.stats.stopReason && document.getElementById('lmsStopReasonVal')) {
|
|
3560
|
+
document.getElementById('lmsStopReasonVal').textContent = data.stats.stopReason;
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
const viewport = document.getElementById('lmsLogViewport');
|
|
3565
|
+
if (!viewport) return;
|
|
3566
|
+
|
|
3567
|
+
if (data.type === 'llm.prediction.input' && data.input) {
|
|
3568
|
+
let queryText = '';
|
|
3569
|
+
const userMatch = data.input.match(/<\|turn>user\s*([\s\S]*?)<turn\|>/);
|
|
3570
|
+
if (userMatch && userMatch[1]) {
|
|
3571
|
+
queryText = userMatch[1].trim();
|
|
3572
|
+
} else {
|
|
3573
|
+
queryText = data.input.slice(-300).trim();
|
|
3574
|
+
}
|
|
3575
|
+
if (queryText) {
|
|
3576
|
+
const el = document.createElement('div');
|
|
3577
|
+
el.className = 'lms-entry-input';
|
|
3578
|
+
el.dataset.lmsType = 'all';
|
|
3579
|
+
if (lmsFilter === 'thoughts' || lmsFilter === 'tools') el.style.display = 'none';
|
|
3580
|
+
el.innerHTML = '<span class="lms-input-badge">Prompt Received</span><br>' + escapeHtml(queryText);
|
|
3581
|
+
viewport.appendChild(el);
|
|
3582
|
+
if (document.getElementById('lmsStopReasonVal')) {
|
|
3583
|
+
document.getElementById('lmsStopReasonVal').textContent = 'Predicting...';
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
while (viewport.children.length > MAX_LMS_LOGS) {
|
|
3587
|
+
viewport.removeChild(viewport.children[0]);
|
|
3588
|
+
}
|
|
3589
|
+
if (lmsAutoScroll) viewport.scrollTop = viewport.scrollHeight;
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
const outputText = data.output || data.raw || (typeof item === 'string' ? item : '');
|
|
3594
|
+
if (!outputText && !item.error && !item.info) return;
|
|
3595
|
+
|
|
3596
|
+
if (item.error) {
|
|
3597
|
+
const div = document.createElement('div');
|
|
3598
|
+
div.className = 'lms-entry-raw';
|
|
3599
|
+
div.style.color = 'var(--accent-red)';
|
|
3600
|
+
div.textContent = item.error;
|
|
3601
|
+
viewport.appendChild(div);
|
|
3602
|
+
} else if (item.info) {
|
|
3603
|
+
const div = document.createElement('div');
|
|
3604
|
+
div.className = 'lms-entry-text';
|
|
3605
|
+
div.style.color = 'var(--accent-violet)';
|
|
3606
|
+
div.textContent = item.info;
|
|
3607
|
+
viewport.appendChild(div);
|
|
3608
|
+
} else {
|
|
3609
|
+
renderLmsOutputEntries(outputText, item, viewport);
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
while (viewport.children.length > MAX_LMS_LOGS) {
|
|
3613
|
+
viewport.removeChild(viewport.children[0]);
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
if (lmsAutoScroll) {
|
|
3617
|
+
viewport.scrollTop = viewport.scrollHeight;
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
function renderLmsOutputEntries(text, item, viewport) {
|
|
3622
|
+
let cur = text;
|
|
3623
|
+
let thoughtPart = '';
|
|
3624
|
+
let answerPart = '';
|
|
3625
|
+
|
|
3626
|
+
if (cur.indexOf('<channel|>') !== -1) {
|
|
3627
|
+
const splitIdx = cur.indexOf('<channel|>');
|
|
3628
|
+
thoughtPart = cur.slice(0, splitIdx).replace(/^<\|channel>thought\s*/, '').trim();
|
|
3629
|
+
answerPart = cur.slice(splitIdx + 10).trim();
|
|
3630
|
+
} else if (cur.indexOf('<|channel>thought') !== -1) {
|
|
3631
|
+
thoughtPart = cur.replace(/^<\|channel>thought\s*/, '').trim();
|
|
3632
|
+
} else {
|
|
3633
|
+
answerPart = cur.trim();
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
if (thoughtPart) {
|
|
3637
|
+
const el = document.createElement('div');
|
|
3638
|
+
el.className = 'lms-entry-thought';
|
|
3639
|
+
el.dataset.lmsType = 'thoughts';
|
|
3640
|
+
if (lmsFilter !== 'all' && lmsFilter !== 'thoughts') el.style.display = 'none';
|
|
3641
|
+
el.innerHTML = '<span class="lms-thought-badge">Reasoning Thought</span><br>' + escapeHtml(thoughtPart);
|
|
3642
|
+
viewport.appendChild(el);
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
if (answerPart) {
|
|
3646
|
+
let remaining = answerPart;
|
|
3647
|
+
while (remaining.indexOf('<|tool_call>') !== -1) {
|
|
3648
|
+
const tStart = remaining.indexOf('<|tool_call>');
|
|
3649
|
+
const beforeTool = remaining.slice(0, tStart).trim();
|
|
3650
|
+
if (beforeTool) {
|
|
3651
|
+
const el = document.createElement('div');
|
|
3652
|
+
el.className = 'lms-entry-answer';
|
|
3653
|
+
el.dataset.lmsType = 'all';
|
|
3654
|
+
if (lmsFilter === 'thoughts' || lmsFilter === 'tools') el.style.display = 'none';
|
|
3655
|
+
el.innerHTML = '<span class="lms-answer-badge">Generated Text</span><br>' + escapeHtml(beforeTool);
|
|
3656
|
+
viewport.appendChild(el);
|
|
3657
|
+
}
|
|
3658
|
+
const tEnd = remaining.indexOf('<tool_call|>', tStart);
|
|
3659
|
+
if (tEnd !== -1) {
|
|
3660
|
+
const toolContent = remaining.slice(tStart + 12, tEnd).trim();
|
|
3661
|
+
const el = document.createElement('div');
|
|
3662
|
+
el.className = 'lms-entry-tool';
|
|
3663
|
+
el.dataset.lmsType = 'tools';
|
|
3664
|
+
if (lmsFilter !== 'all' && lmsFilter !== 'tools') el.style.display = 'none';
|
|
3665
|
+
el.innerHTML = '<span class="lms-tool-badge">Tool Invocation</span><br>' + escapeHtml(toolContent);
|
|
3666
|
+
viewport.appendChild(el);
|
|
3667
|
+
remaining = remaining.slice(tEnd + 12).trim();
|
|
3668
|
+
} else {
|
|
3669
|
+
break;
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
if (remaining) {
|
|
3674
|
+
const el = document.createElement('div');
|
|
3675
|
+
el.className = 'lms-entry-answer';
|
|
3676
|
+
el.dataset.lmsType = 'all';
|
|
3677
|
+
if (lmsFilter === 'thoughts' || lmsFilter === 'tools') el.style.display = 'none';
|
|
3678
|
+
el.innerHTML = '<span class="lms-answer-badge">Generated Answer</span><br>' + escapeHtml(remaining);
|
|
3679
|
+
viewport.appendChild(el);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
const rawEl = document.createElement('div');
|
|
3684
|
+
rawEl.className = 'lms-entry-raw';
|
|
3685
|
+
rawEl.dataset.lmsType = 'json';
|
|
3686
|
+
if (lmsFilter !== 'json') rawEl.style.display = 'none';
|
|
3687
|
+
rawEl.textContent = JSON.stringify(item);
|
|
3688
|
+
viewport.appendChild(rawEl);
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
function setLmsFilter(filter) {
|
|
3692
|
+
lmsFilter = filter;
|
|
3693
|
+
document.querySelectorAll('.lms-filter-btn').forEach(btn => {
|
|
3694
|
+
btn.classList.toggle('active', btn.dataset.lmsFilter === filter);
|
|
3695
|
+
});
|
|
3696
|
+
const viewport = document.getElementById('lmsLogViewport');
|
|
3697
|
+
if (!viewport) return;
|
|
3698
|
+
Array.from(viewport.children).forEach(child => {
|
|
3699
|
+
if (child.id === 'lmsEmptyNotice') return;
|
|
3700
|
+
const type = child.dataset.lmsType;
|
|
3701
|
+
if (filter === 'all') {
|
|
3702
|
+
child.style.display = (type === 'json') ? 'none' : '';
|
|
3703
|
+
} else if (filter === 'json') {
|
|
3704
|
+
child.style.display = (type === 'json') ? '' : 'none';
|
|
3705
|
+
} else {
|
|
3706
|
+
child.style.display = (type === filter) ? '' : 'none';
|
|
3707
|
+
}
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
function toggleLmsAutoScroll() {
|
|
3712
|
+
lmsAutoScroll = !lmsAutoScroll;
|
|
3713
|
+
const btn = document.getElementById('lmsAutoScrollBtn');
|
|
3714
|
+
if (btn) {
|
|
3715
|
+
btn.textContent = 'Auto-scroll: ' + (lmsAutoScroll ? 'ON' : 'OFF');
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
function clearLmsLogs() {
|
|
3720
|
+
const viewport = document.getElementById('lmsLogViewport');
|
|
3721
|
+
if (viewport) {
|
|
3722
|
+
viewport.innerHTML = '<div class="lms-log-empty" id="lmsEmptyNotice">Listening for LM Studio inference events...</div>';
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
function toggleLmsPanel() {
|
|
3727
|
+
const body = document.getElementById('lmsStreamBody');
|
|
3728
|
+
const chev = document.getElementById('lmsChevron');
|
|
3729
|
+
if (!body) return;
|
|
3730
|
+
const isHidden = body.style.display === 'none';
|
|
3731
|
+
body.style.display = isHidden ? 'flex' : 'none';
|
|
3732
|
+
if (chev) chev.classList.toggle('open', isHidden);
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
function initSSE() {
|
|
3736
|
+
const dot = document.getElementById('sseStatusDot');
|
|
3737
|
+
const text = document.getElementById('sseStatusText');
|
|
3738
|
+
|
|
3739
|
+
if (eventSource) {
|
|
3740
|
+
eventSource.close();
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
eventSource = new EventSource('/api/eval/events');
|
|
3744
|
+
|
|
3745
|
+
eventSource.onopen = () => {
|
|
3746
|
+
dot.className = isEvaluationRunning ? 'status-dot running' : 'status-dot active';
|
|
3747
|
+
text.textContent = isEvaluationRunning ? 'Running' : 'Live SSE';
|
|
3748
|
+
};
|
|
3749
|
+
|
|
3750
|
+
eventSource.onerror = () => {
|
|
3751
|
+
dot.className = 'status-dot';
|
|
3752
|
+
text.textContent = 'Disconnected';
|
|
3753
|
+
};
|
|
3754
|
+
|
|
3755
|
+
eventSource.addEventListener('sync_state', e => {
|
|
3756
|
+
try {
|
|
3757
|
+
const data = JSON.parse(e.data);
|
|
3758
|
+
if (data.running) {
|
|
3759
|
+
setRunningUiState(true);
|
|
3760
|
+
if (data.activeRun?.startedAt) {
|
|
3761
|
+
runStartTime = data.activeRun.startedAt;
|
|
3762
|
+
if (runTimerInterval) clearInterval(runTimerInterval);
|
|
3763
|
+
runTimerInterval = setInterval(updateRunTimer, 1000);
|
|
3764
|
+
}
|
|
3765
|
+
if (data.activeRun?.suite) {
|
|
3766
|
+
document.getElementById('runTitle').textContent = 'Suite: ' + data.activeRun.suite;
|
|
3767
|
+
}
|
|
3768
|
+
if (data.activeCase) {
|
|
3769
|
+
handleCaseStart(data.activeCase);
|
|
3770
|
+
if (data.activeCase.tool) {
|
|
3771
|
+
document.getElementById('activeToolText').textContent = data.activeCase.tool;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
if (data.report && Array.isArray(data.report.rows)) {
|
|
3775
|
+
currentReport = data.report;
|
|
3776
|
+
saveReportToStorage(currentReport);
|
|
3777
|
+
renderDashboard();
|
|
3778
|
+
const overallTotal = data.activeRun?.total || data.report?.metadata?.caseIds?.length || ${totalCount};
|
|
3779
|
+
const batchInfo = (data.activeRun?.planned && data.activeRun.planned !== overallTotal)
|
|
3780
|
+
? { current: data.activeCase?.index || (data.report.rows.length - (data.activeRun.initialRows || 0)), total: data.activeRun.planned }
|
|
3781
|
+
: null;
|
|
3782
|
+
updateProgress(data.report.rows.length, overallTotal, batchInfo);
|
|
3783
|
+
}
|
|
3784
|
+
showSyncNotice('Live run connected');
|
|
3785
|
+
} else if (data.report && Array.isArray(data.report.rows) && data.report.rows.length > 0) {
|
|
3786
|
+
currentReport = data.report;
|
|
3787
|
+
saveReportToStorage(currentReport);
|
|
3788
|
+
renderDashboard();
|
|
3789
|
+
const total = data.report.metadata?.caseIds?.length || ${totalCount};
|
|
3790
|
+
document.getElementById('progressHero').style.display = 'flex';
|
|
3791
|
+
document.getElementById('runTitle').textContent = 'Live Report (' + currentReport.rows.length + ' cases)';
|
|
3792
|
+
updateProgress(currentReport.rows.length, total);
|
|
3793
|
+
}
|
|
3794
|
+
} catch {}
|
|
3795
|
+
});
|
|
3796
|
+
|
|
3797
|
+
eventSource.addEventListener('run_start', e => {
|
|
3798
|
+
const data = JSON.parse(e.data);
|
|
3799
|
+
handleRunStart(data);
|
|
3800
|
+
});
|
|
3801
|
+
|
|
3802
|
+
eventSource.addEventListener('case_start', e => {
|
|
3803
|
+
const data = JSON.parse(e.data);
|
|
3804
|
+
handleCaseStart(data);
|
|
3805
|
+
});
|
|
3806
|
+
|
|
3807
|
+
eventSource.addEventListener('case_step', e => {
|
|
3808
|
+
const data = JSON.parse(e.data);
|
|
3809
|
+
handleCaseStep(data);
|
|
3810
|
+
});
|
|
3811
|
+
|
|
3812
|
+
eventSource.addEventListener('case_complete', e => {
|
|
3813
|
+
const data = JSON.parse(e.data);
|
|
3814
|
+
handleCaseComplete(data);
|
|
3815
|
+
});
|
|
3816
|
+
|
|
3817
|
+
eventSource.addEventListener('run_complete', e => {
|
|
3818
|
+
const data = JSON.parse(e.data);
|
|
3819
|
+
handleRunComplete(data);
|
|
3820
|
+
});
|
|
3821
|
+
|
|
3822
|
+
eventSource.addEventListener('report_judged', e => {
|
|
3823
|
+
try {
|
|
3824
|
+
const data = JSON.parse(e.data);
|
|
3825
|
+
if (data.report) {
|
|
3826
|
+
currentReport = data.report;
|
|
3827
|
+
saveReportToStorage(currentReport);
|
|
3828
|
+
renderDashboard();
|
|
3829
|
+
showSyncNotice('Report graded with Judge');
|
|
3830
|
+
}
|
|
3831
|
+
} catch {}
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
async function checkServerStatus() {
|
|
3836
|
+
try {
|
|
3837
|
+
const res = await fetch('/api/eval/status');
|
|
3838
|
+
if (res.ok) {
|
|
3839
|
+
const status = await res.json();
|
|
3840
|
+
if (status.running) {
|
|
3841
|
+
setRunningUiState(true);
|
|
3842
|
+
if (status.activeRun?.startedAt) {
|
|
3843
|
+
runStartTime = status.activeRun.startedAt;
|
|
3844
|
+
if (runTimerInterval) clearInterval(runTimerInterval);
|
|
3845
|
+
runTimerInterval = setInterval(updateRunTimer, 1000);
|
|
3846
|
+
}
|
|
3847
|
+
if (status.activeRun?.suite) {
|
|
3848
|
+
document.getElementById('runTitle').textContent = 'Suite: ' + status.activeRun.suite;
|
|
3849
|
+
}
|
|
3850
|
+
if (status.activeCase) {
|
|
3851
|
+
handleCaseStart(status.activeCase);
|
|
3852
|
+
if (status.activeCase.tool) {
|
|
3853
|
+
document.getElementById('activeToolText').textContent = status.activeCase.tool;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
if (status.currentReport && Array.isArray(status.currentReport.rows)) {
|
|
3857
|
+
currentReport = status.currentReport;
|
|
3858
|
+
saveReportToStorage(currentReport);
|
|
3859
|
+
renderDashboard();
|
|
3860
|
+
const overallTotal = status.activeRun?.total || status.currentReport.metadata?.caseIds?.length || ${totalCount};
|
|
3861
|
+
const batchInfo = (status.activeRun?.planned && status.activeRun.planned !== overallTotal)
|
|
3862
|
+
? { current: status.activeCase?.index, total: status.activeRun.planned }
|
|
3863
|
+
: null;
|
|
3864
|
+
updateProgress(status.currentReport.rows.length, overallTotal, batchInfo);
|
|
3865
|
+
}
|
|
3866
|
+
showSyncNotice('Synced live test');
|
|
3867
|
+
} else {
|
|
3868
|
+
setRunningUiState(false);
|
|
3869
|
+
if (status.currentReport && Array.isArray(status.currentReport.rows) && status.currentReport.rows.length > 0) {
|
|
3870
|
+
currentReport = status.currentReport;
|
|
3871
|
+
saveReportToStorage(currentReport);
|
|
3872
|
+
renderDashboard();
|
|
3873
|
+
} else if (status.hasReport && !currentReport.rows?.length) {
|
|
3874
|
+
loadLatestReport();
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
} catch (err) {}
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
function setRunningUiState(isRunning) {
|
|
3882
|
+
isEvaluationRunning = isRunning;
|
|
3883
|
+
document.getElementById('btnStart').disabled = isRunning;
|
|
3884
|
+
document.getElementById('btnStop').disabled = !isRunning;
|
|
3885
|
+
const drawerBtn = document.getElementById('drawerRunBtn');
|
|
3886
|
+
if (drawerBtn) drawerBtn.disabled = isRunning;
|
|
3887
|
+
const progressHero = document.getElementById('progressHero');
|
|
3888
|
+
const activeCard = document.getElementById('activeCaseCard');
|
|
3889
|
+
const dot = document.getElementById('sseStatusDot');
|
|
3890
|
+
|
|
3891
|
+
if (isRunning) {
|
|
3892
|
+
progressHero.style.display = 'flex';
|
|
3893
|
+
activeCard.style.display = 'flex';
|
|
3894
|
+
dot.className = 'status-dot running';
|
|
3895
|
+
document.getElementById('runStatusBadge').className = 'badge-gate badge-neutral';
|
|
3896
|
+
document.getElementById('runStatusBadge').textContent = 'RUNNING';
|
|
3897
|
+
if (window.innerWidth < 768) {
|
|
3898
|
+
document.getElementById('configBody').style.display = 'none';
|
|
3899
|
+
document.getElementById('configChevron').classList.remove('open');
|
|
3900
|
+
}
|
|
3901
|
+
} else {
|
|
3902
|
+
activeCard.style.display = 'none';
|
|
3903
|
+
dot.className = 'status-dot active';
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
async function startRun() {
|
|
3908
|
+
const suite = document.getElementById('suiteSelect').value;
|
|
3909
|
+
const model = document.getElementById('modelInput').value;
|
|
3910
|
+
const baseUrl = document.getElementById('baseUrlInput').value;
|
|
3911
|
+
const limitVal = document.getElementById('limitInput').value;
|
|
3912
|
+
const judgeModel = document.getElementById('judgeModelInput').value;
|
|
3913
|
+
const targetCase = document.getElementById('caseSelect')?.value;
|
|
3914
|
+
|
|
3915
|
+
let payloadSuite = suite;
|
|
3916
|
+
let payloadLimit = limitVal ? Number(limitVal) : undefined;
|
|
3917
|
+
let payloadIds = undefined;
|
|
3918
|
+
|
|
3919
|
+
if (suite === 'single') {
|
|
3920
|
+
payloadSuite = 'all';
|
|
3921
|
+
payloadLimit = 1;
|
|
3922
|
+
payloadIds = targetCase || 'p01';
|
|
3923
|
+
} else if (suite === 'quick') {
|
|
3924
|
+
payloadSuite = 'catalog';
|
|
3925
|
+
payloadLimit = 3;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
if (window.customSuiteRunIds) {
|
|
3929
|
+
payloadSuite = 'all';
|
|
3930
|
+
payloadIds = window.customSuiteRunIds;
|
|
3931
|
+
payloadLimit = undefined;
|
|
3932
|
+
window.customSuiteRunIds = null;
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
const isResume = Boolean(document.getElementById('resumeCheckbox')?.checked);
|
|
3936
|
+
const payload = {
|
|
3937
|
+
suite: payloadSuite,
|
|
3938
|
+
ids: payloadIds,
|
|
3939
|
+
limit: payloadLimit,
|
|
3940
|
+
model: model.trim() || undefined,
|
|
3941
|
+
baseUrl: baseUrl.trim() || undefined,
|
|
3942
|
+
judgeModel: judgeModel.trim() || undefined,
|
|
3943
|
+
resume: isResume
|
|
3944
|
+
};
|
|
3945
|
+
|
|
3946
|
+
if (!payloadIds && !isResume) {
|
|
3947
|
+
currentReport = { rows: [] };
|
|
3948
|
+
renderDashboard();
|
|
3949
|
+
}
|
|
3950
|
+
setRunningUiState(true);
|
|
3951
|
+
|
|
3952
|
+
runStartTime = Date.now();
|
|
3953
|
+
if (runTimerInterval) clearInterval(runTimerInterval);
|
|
3954
|
+
runTimerInterval = setInterval(updateRunTimer, 1000);
|
|
3955
|
+
|
|
3956
|
+
try {
|
|
3957
|
+
const res = await fetch('/api/eval/start', {
|
|
3958
|
+
method: 'POST',
|
|
3959
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3960
|
+
body: JSON.stringify(payload)
|
|
3961
|
+
});
|
|
3962
|
+
if (!res.ok) {
|
|
3963
|
+
const err = await res.json();
|
|
3964
|
+
await showStudioAlert('Run Evaluation Failed', err.error || res.statusText, 'error');
|
|
3965
|
+
setRunningUiState(false);
|
|
3966
|
+
clearInterval(runTimerInterval);
|
|
3967
|
+
}
|
|
3968
|
+
} catch (err) {
|
|
3969
|
+
await showStudioAlert('Connection Error', 'Network error starting evaluation: ' + err.message, 'error');
|
|
3970
|
+
setRunningUiState(false);
|
|
3971
|
+
clearInterval(runTimerInterval);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
async function runSingleCaseById(caseId) {
|
|
3976
|
+
if (!caseId) return;
|
|
3977
|
+
if (document.getElementById('btnStart').disabled) return;
|
|
3978
|
+
const model = document.getElementById('modelInput').value;
|
|
3979
|
+
const baseUrl = document.getElementById('baseUrlInput').value;
|
|
3980
|
+
const judgeModel = document.getElementById('judgeModelInput').value;
|
|
3981
|
+
|
|
3982
|
+
const payload = {
|
|
3983
|
+
suite: 'all',
|
|
3984
|
+
ids: caseId,
|
|
3985
|
+
limit: 1,
|
|
3986
|
+
model: model.trim() || undefined,
|
|
3987
|
+
baseUrl: baseUrl.trim() || undefined,
|
|
3988
|
+
judgeModel: judgeModel.trim() || undefined
|
|
3989
|
+
};
|
|
3990
|
+
|
|
3991
|
+
const sel = document.getElementById('caseSelect');
|
|
3992
|
+
if (sel) sel.value = caseId;
|
|
3993
|
+
|
|
3994
|
+
setRunningUiState(true);
|
|
3995
|
+
runStartTime = Date.now();
|
|
3996
|
+
if (runTimerInterval) clearInterval(runTimerInterval);
|
|
3997
|
+
runTimerInterval = setInterval(updateRunTimer, 1000);
|
|
3998
|
+
|
|
3999
|
+
try {
|
|
4000
|
+
const res = await fetch('/api/eval/start', {
|
|
4001
|
+
method: 'POST',
|
|
4002
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4003
|
+
body: JSON.stringify(payload)
|
|
4004
|
+
});
|
|
4005
|
+
if (!res.ok) {
|
|
4006
|
+
const err = await res.json();
|
|
4007
|
+
await showStudioAlert('Case Run Failed', 'Failed to start case ' + caseId + ': ' + (err.error || res.statusText), 'error');
|
|
4008
|
+
setRunningUiState(false);
|
|
4009
|
+
clearInterval(runTimerInterval);
|
|
4010
|
+
}
|
|
4011
|
+
} catch (err) {
|
|
4012
|
+
await showStudioAlert('Connection Error', err.message, 'error');
|
|
4013
|
+
setRunningUiState(false);
|
|
4014
|
+
clearInterval(runTimerInterval);
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
async function stopRun() {
|
|
4019
|
+
try {
|
|
4020
|
+
await fetch('/api/eval/stop', { method: 'POST' });
|
|
4021
|
+
document.getElementById('activeToolText').textContent = 'Aborting';
|
|
4022
|
+
} catch (err) {}
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
function formatEta(remainingMs) {
|
|
4026
|
+
if (!remainingMs || remainingMs <= 0) return null;
|
|
4027
|
+
const totalSec = Math.round(remainingMs / 1000);
|
|
4028
|
+
const now = new Date(Date.now() + remainingMs);
|
|
4029
|
+
const hh = now.getHours().toString().padStart(2, '0');
|
|
4030
|
+
const mm = now.getMinutes().toString().padStart(2, '0');
|
|
4031
|
+
const hRemain = Math.floor(totalSec / 3600);
|
|
4032
|
+
const mRemain = Math.floor((totalSec % 3600) / 60);
|
|
4033
|
+
const durationStr = hRemain > 0 ? (hRemain + 'h ' + mRemain + 'm') : (mRemain + 'm ' + (totalSec % 60) + 's');
|
|
4034
|
+
return 'ETA ' + hh + ':' + mm + ' (' + durationStr + ' remaining)';
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
let currentProgressMeta = { current: 0, total: 0, batchInfo: null };
|
|
4038
|
+
|
|
4039
|
+
function getElapsedSeconds() {
|
|
4040
|
+
if (!runStartTime) return 0;
|
|
4041
|
+
return Math.max(0, Math.floor((Date.now() - runStartTime) / 1000));
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
function formatElapsedString(elapsedSec) {
|
|
4045
|
+
const m = Math.floor(elapsedSec / 60);
|
|
4046
|
+
const s = elapsedSec % 60;
|
|
4047
|
+
return m > 0 ? (m + 'm ' + s + 's') : (s + 's');
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
function renderEta(current, total, batchInfo) {
|
|
4051
|
+
const etaElem = document.getElementById('etaDisplay');
|
|
4052
|
+
if (!etaElem) return;
|
|
4053
|
+
if (current >= total || !runStartTime) {
|
|
4054
|
+
etaElem.style.display = 'none';
|
|
4055
|
+
return;
|
|
4056
|
+
}
|
|
4057
|
+
let remaining = total - current;
|
|
4058
|
+
let effectiveDone = current;
|
|
4059
|
+
if (batchInfo && batchInfo.current > 0 && batchInfo.total > 0) {
|
|
4060
|
+
remaining = batchInfo.total - batchInfo.current;
|
|
4061
|
+
effectiveDone = batchInfo.current;
|
|
4062
|
+
}
|
|
4063
|
+
const elapsedMs = Date.now() - runStartTime;
|
|
4064
|
+
if (effectiveDone > 0 && remaining > 0 && elapsedMs > 3000) {
|
|
4065
|
+
const msPerCase = elapsedMs / effectiveDone;
|
|
4066
|
+
const remainingMs = remaining * msPerCase;
|
|
4067
|
+
const etaStr = formatEta(remainingMs);
|
|
4068
|
+
if (etaStr) {
|
|
4069
|
+
etaElem.textContent = etaStr;
|
|
4070
|
+
etaElem.style.display = 'block';
|
|
4071
|
+
return;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
if (currentReport?.summary?.meanLlmMs && remaining > 0) {
|
|
4075
|
+
const etaMs = remaining * currentReport.summary.meanLlmMs;
|
|
4076
|
+
const etaStr = formatEta(etaMs);
|
|
4077
|
+
if (etaStr) {
|
|
4078
|
+
etaElem.textContent = etaStr;
|
|
4079
|
+
etaElem.style.display = 'block';
|
|
4080
|
+
return;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
etaElem.style.display = 'none';
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
function updateRunTimer() {
|
|
4087
|
+
if (!runStartTime) return;
|
|
4088
|
+
const elapsedSec = getElapsedSeconds();
|
|
4089
|
+
const timeStr = formatElapsedString(elapsedSec);
|
|
4090
|
+
const statsElem = document.getElementById('progressStats');
|
|
4091
|
+
if (statsElem) {
|
|
4092
|
+
const base = statsElem.dataset.baseText || statsElem.textContent.split('• Elapsed:')[0].trim();
|
|
4093
|
+
statsElem.textContent = base + ' • Elapsed: ' + timeStr;
|
|
4094
|
+
}
|
|
4095
|
+
if (currentProgressMeta.total > 0 && currentProgressMeta.current > 0) {
|
|
4096
|
+
renderEta(currentProgressMeta.current, currentProgressMeta.total, currentProgressMeta.batchInfo);
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
function handleRunStart(data) {
|
|
4101
|
+
setRunningUiState(true);
|
|
4102
|
+
document.getElementById('runTitle').textContent = 'Suite: ' + (data.suite || 'catalog');
|
|
4103
|
+
const overallTotal = data.overallTotal || data.total || 0;
|
|
4104
|
+
const initial = data.initialRows || currentReport.rows?.length || 0;
|
|
4105
|
+
const batchInfo = (data.planned && data.planned !== overallTotal) ? { current: 0, total: data.planned } : null;
|
|
4106
|
+
updateProgress(initial, overallTotal, batchInfo);
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
function handleCaseStart(data) {
|
|
4110
|
+
document.getElementById('activeCaseBadge').textContent = '#' + data.index + ' ' + data.caseId;
|
|
4111
|
+
document.getElementById('activeCaseText').textContent = data.question || '';
|
|
4112
|
+
document.getElementById('activeToolText').textContent = 'Starting agent';
|
|
4113
|
+
const overallTotal = data.overallTotal || data.total;
|
|
4114
|
+
const overallIdx = data.overallIndex != null ? Math.max(0, data.overallIndex - 1) : (currentReport.rows?.length || 0);
|
|
4115
|
+
const batchInfo = (data.total && data.total !== overallTotal) ? { current: data.index, total: data.total } : null;
|
|
4116
|
+
updateProgress(overallIdx, overallTotal, batchInfo);
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
function handleCaseStep(data) {
|
|
4120
|
+
if (data.event === 'retry') {
|
|
4121
|
+
document.getElementById('activeToolText').textContent = 'Reconnecting (attempt ' + data.attempt + '/3)...';
|
|
4122
|
+
} else if (data.event === 'tool_start') {
|
|
4123
|
+
document.getElementById('activeToolText').textContent = data.name;
|
|
4124
|
+
} else if (data.event === 'tool_result') {
|
|
4125
|
+
document.getElementById('activeToolText').textContent = data.name + ' (' + (data.durationMs || 0) + 'ms)';
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
function handleCaseComplete(data) {
|
|
4130
|
+
if (!currentReport.rows) currentReport.rows = [];
|
|
4131
|
+
const idx = currentReport.rows.findIndex(r => r.id === data.result.id);
|
|
4132
|
+
if (idx >= 0) {
|
|
4133
|
+
currentReport.rows[idx] = data.result;
|
|
4134
|
+
} else {
|
|
4135
|
+
currentReport.rows.push(data.result);
|
|
4136
|
+
}
|
|
4137
|
+
if (data.summary) {
|
|
4138
|
+
currentReport.summary = data.summary;
|
|
4139
|
+
} else if (data.report?.summary) {
|
|
4140
|
+
currentReport.summary = data.report.summary;
|
|
4141
|
+
}
|
|
4142
|
+
saveReportToStorage(currentReport);
|
|
4143
|
+
const overallTotal = data.overallTotal || currentReport.metadata?.caseIds?.length || data.total;
|
|
4144
|
+
const overallCurrent = data.overallCurrent || currentReport.rows.length;
|
|
4145
|
+
const batchInfo = (data.total && data.total !== overallTotal) ? { current: data.current, total: data.total } : null;
|
|
4146
|
+
updateProgress(overallCurrent, overallTotal, batchInfo);
|
|
4147
|
+
renderDashboard();
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
function handleRunComplete(data) {
|
|
4151
|
+
isEvaluationRunning = false;
|
|
4152
|
+
setRunningUiState(false);
|
|
4153
|
+
if (runTimerInterval) clearInterval(runTimerInterval);
|
|
4154
|
+
if (data.error) {
|
|
4155
|
+
document.getElementById('runStatusBadge').className = 'badge-gate badge-fail';
|
|
4156
|
+
document.getElementById('runStatusBadge').textContent = 'ERROR';
|
|
4157
|
+
document.getElementById('runTitle').textContent = 'Evaluation Error: ' + data.error;
|
|
4158
|
+
showStudioAlert('Evaluation Error', data.error, 'error');
|
|
4159
|
+
return;
|
|
4160
|
+
}
|
|
4161
|
+
document.getElementById('runStatusBadge').className = data.gatesPassed ? 'badge-gate badge-pass' : 'badge-gate badge-fail';
|
|
4162
|
+
document.getElementById('runStatusBadge').textContent = data.gatesPassed ? 'GATES PASSED' : 'GATES FAILED';
|
|
4163
|
+
document.getElementById('runTitle').textContent = 'Evaluation Finished';
|
|
4164
|
+
if (data.report) {
|
|
4165
|
+
currentReport = data.report;
|
|
4166
|
+
saveReportToStorage(currentReport);
|
|
4167
|
+
renderDashboard();
|
|
4168
|
+
const total = currentReport.metadata?.caseIds?.length || currentReport.rows.length;
|
|
4169
|
+
updateProgress(currentReport.rows.length, total);
|
|
4170
|
+
}
|
|
4171
|
+
showSyncNotice('Results auto-saved');
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4174
|
+
function updateProgress(current, total, batchInfo = null) {
|
|
4175
|
+
const safeTotal = Math.max(1, Number(total) || 1);
|
|
4176
|
+
const safeCurrent = Math.min(safeTotal, Math.max(0, Number(current) || 0));
|
|
4177
|
+
const pct = Math.min(100, Math.max(0, Math.round((safeCurrent / safeTotal) * 100)));
|
|
4178
|
+
currentProgressMeta = { current: safeCurrent, total: safeTotal, batchInfo };
|
|
4179
|
+
|
|
4180
|
+
const fillEl = document.getElementById('progressBarFill');
|
|
4181
|
+
if (fillEl) fillEl.style.width = pct + '%';
|
|
4182
|
+
|
|
4183
|
+
const stats = document.getElementById('progressStats');
|
|
4184
|
+
if (stats) {
|
|
4185
|
+
let base = safeCurrent + ' / ' + safeTotal + ' cases (' + pct + '%)';
|
|
4186
|
+
if (batchInfo && batchInfo.current != null && batchInfo.total && batchInfo.total !== safeTotal) {
|
|
4187
|
+
base += ' [Batch: ' + batchInfo.current + '/' + batchInfo.total + ']';
|
|
4188
|
+
}
|
|
4189
|
+
stats.dataset.baseText = base;
|
|
4190
|
+
const elapsedSec = getElapsedSeconds();
|
|
4191
|
+
const timeStr = formatElapsedString(elapsedSec);
|
|
4192
|
+
stats.textContent = base + ' • Elapsed: ' + timeStr;
|
|
4193
|
+
}
|
|
4194
|
+
renderEta(safeCurrent, safeTotal, batchInfo);
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
async function loadLatestReport() {
|
|
4198
|
+
try {
|
|
4199
|
+
const res = await fetch('/api/eval/report');
|
|
4200
|
+
if (res.ok) {
|
|
4201
|
+
const loaded = await res.json();
|
|
4202
|
+
if (loaded && Array.isArray(loaded.rows) && loaded.rows.length > 0) {
|
|
4203
|
+
currentReport = loaded;
|
|
4204
|
+
saveReportToStorage(currentReport);
|
|
4205
|
+
renderDashboard();
|
|
4206
|
+
const total = loaded.metadata?.caseIds?.length || ${totalCount};
|
|
4207
|
+
document.getElementById('progressHero').style.display = 'flex';
|
|
4208
|
+
document.getElementById('runTitle').textContent = 'Live Report (' + currentReport.rows.length + ' cases)';
|
|
4209
|
+
updateProgress(currentReport.rows.length, total);
|
|
4210
|
+
showSyncNotice('Loaded live.json (' + currentReport.rows.length + ' cases)');
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
} catch (err) {}
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
function renderDashboard() {
|
|
4217
|
+
const s = currentReport.summary || {};
|
|
4218
|
+
const rows = currentReport.rows || [];
|
|
4219
|
+
|
|
4220
|
+
renderKpis(s, rows);
|
|
4221
|
+
renderGateStatus(s);
|
|
4222
|
+
renderCounts(rows);
|
|
4223
|
+
renderCardsAndTable(rows);
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
function renderKpis(s, rows) {
|
|
4227
|
+
const formatPct = val => val !== null && val !== undefined ? Math.round(val * 100) + '%' : '-';
|
|
4228
|
+
const formatRatio = val => val !== null && val !== undefined ? Number(val).toFixed(2) : '-';
|
|
4229
|
+
|
|
4230
|
+
const passAt1 = s.passAt1 ?? s.lexicalCoverageRate;
|
|
4231
|
+
document.getElementById('kpiPassAt1').textContent = formatPct(passAt1);
|
|
4232
|
+
setGatePill('gatePassAt1', passAt1, 0.75, true);
|
|
4233
|
+
|
|
4234
|
+
const toolF1 = s.toolF1 ?? s.toolAccuracyRate;
|
|
4235
|
+
document.getElementById('kpiToolF1').textContent = formatRatio(toolF1);
|
|
4236
|
+
setGatePill('gateToolF1', toolF1, 0.90, true);
|
|
4237
|
+
|
|
4238
|
+
const grounded = s.groundednessRate;
|
|
4239
|
+
document.getElementById('kpiGroundedness').textContent = formatPct(grounded);
|
|
4240
|
+
setGatePill('gateGroundedness', grounded, 0.75, true);
|
|
4241
|
+
|
|
4242
|
+
const loopFree = s.loopFreeRate;
|
|
4243
|
+
document.getElementById('kpiLoopFree').textContent = formatPct(loopFree);
|
|
4244
|
+
setGatePill('gateLoopFree', loopFree, 0.95, true);
|
|
4245
|
+
|
|
4246
|
+
const hallucination = s.hallucinationRate;
|
|
4247
|
+
document.getElementById('kpiHallucination').textContent = formatPct(hallucination);
|
|
4248
|
+
setGatePill('gateHallucination', hallucination, 0.05, false);
|
|
4249
|
+
|
|
4250
|
+
const medMs = s.medianMs;
|
|
4251
|
+
document.getElementById('kpiLatency').textContent = medMs ? (medMs / 1000).toFixed(2) + 's' : '-';
|
|
4252
|
+
document.getElementById('kpiP95').textContent = s.p95Ms ? (s.p95Ms / 1000).toFixed(2) + 's' : '-';
|
|
4253
|
+
document.getElementById('kpiTps').textContent = s.meanDecodeTps ? ('Decode: ' + s.meanDecodeTps.toFixed(1) + ' tok/s') : 'Decode: -';
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
function setGatePill(elemId, val, threshold, higherIsBetter) {
|
|
4257
|
+
const elem = document.getElementById(elemId);
|
|
4258
|
+
if (val === null || val === undefined || isNaN(val)) {
|
|
4259
|
+
elem.className = 'badge-gate badge-neutral';
|
|
4260
|
+
elem.textContent = '-';
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4263
|
+
const passed = higherIsBetter ? (val >= threshold) : (val <= threshold);
|
|
4264
|
+
elem.className = passed ? 'badge-gate badge-pass' : 'badge-gate badge-fail';
|
|
4265
|
+
elem.textContent = passed ? 'PASS' : 'FAIL';
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
function renderGateStatus(s) {
|
|
4269
|
+
const overallBadge = document.getElementById('overallGateBadge');
|
|
4270
|
+
const gateSubtext = document.getElementById('gateSubtext');
|
|
4271
|
+
|
|
4272
|
+
if (!s || s.completionRate === null || s.completionRate === undefined) {
|
|
4273
|
+
overallBadge.className = 'badge-gate badge-neutral';
|
|
4274
|
+
overallBadge.textContent = 'PENDING';
|
|
4275
|
+
gateSubtext.textContent = 'Awaiting evaluation run';
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4278
|
+
|
|
4279
|
+
const passAt1Ok = (s.passAt1 ?? s.lexicalCoverageRate ?? 0) >= 0.75;
|
|
4280
|
+
const toolOk = (s.toolF1 ?? s.toolAccuracyRate ?? 0) >= 0.80;
|
|
4281
|
+
const loopOk = (s.loopFreeRate ?? 1) >= 0.95;
|
|
4282
|
+
const allPassed = passAt1Ok && toolOk && loopOk;
|
|
4283
|
+
|
|
4284
|
+
overallBadge.className = allPassed ? 'badge-gate badge-pass' : 'badge-gate badge-fail';
|
|
4285
|
+
overallBadge.textContent = allPassed ? 'PASS' : 'FAIL';
|
|
4286
|
+
gateSubtext.textContent = allPassed
|
|
4287
|
+
? 'Pass@1, Tool F1, and Trajectory Health meet quality benchmark.'
|
|
4288
|
+
: 'One or more metrics are below the quality gate requirements.';
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
function renderCounts(rows) {
|
|
4292
|
+
const anomalies = rows.filter(isAnomalyRow);
|
|
4293
|
+
const errorRows = rows.filter(isErrorRow);
|
|
4294
|
+
document.getElementById('countAll').textContent = rows.length;
|
|
4295
|
+
document.getElementById('countCompleted').textContent = rows.filter(r => r.status === 'completed').length;
|
|
4296
|
+
const countAnomaliesEl = document.getElementById('countAnomalies');
|
|
4297
|
+
if (countAnomaliesEl) countAnomaliesEl.textContent = anomalies.length;
|
|
4298
|
+
document.getElementById('countError').textContent = errorRows.length;
|
|
4299
|
+
document.getElementById('countToolFail').textContent = rows.filter(r => r.toolCorrectness && r.toolCorrectness.isCorrect === false).length;
|
|
4300
|
+
document.getElementById('countLoopFail').textContent = rows.filter(r => r.trajectoryHealth?.hasLoop).length;
|
|
4301
|
+
document.getElementById('countGroundingFail').textContent = rows.filter(r => r.groundedness && r.groundedness.isGrounded === false).length;
|
|
4302
|
+
|
|
4303
|
+
const btnRetry = document.getElementById('btnRetry');
|
|
4304
|
+
const btnRetryLabel = document.getElementById('btnRetryLabel');
|
|
4305
|
+
if (btnRetry && btnRetryLabel) {
|
|
4306
|
+
if (errorRows.length > 0) {
|
|
4307
|
+
btnRetry.style.display = 'inline-flex';
|
|
4308
|
+
btnRetryLabel.textContent = 'Retry (' + errorRows.length + ')';
|
|
4309
|
+
btnRetry.title = 'Retry ' + errorRows.length + ' error cases (status: error)';
|
|
4310
|
+
} else {
|
|
4311
|
+
btnRetry.style.display = 'none';
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
|
|
4316
|
+
function setFilter(filter) {
|
|
4317
|
+
activeFilter = filter;
|
|
4318
|
+
document.querySelectorAll('.filter-tab').forEach(b => {
|
|
4319
|
+
b.classList.toggle('active', b.dataset.filter === filter);
|
|
4320
|
+
});
|
|
4321
|
+
renderCardsAndTable(currentReport.rows || []);
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
function handleSearch() {
|
|
4325
|
+
searchQuery = document.getElementById('searchInput').value.toLowerCase().trim();
|
|
4326
|
+
renderCardsAndTable(currentReport.rows || []);
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
function getFilteredRows(rows) {
|
|
4330
|
+
return rows.filter(r => {
|
|
4331
|
+
if (activeFilter === 'completed' && r.status !== 'completed') return false;
|
|
4332
|
+
if (activeFilter === 'anomalies' && !isAnomalyRow(r)) return false;
|
|
4333
|
+
if (activeFilter === 'error' && (r.status !== 'error' && !r.error)) return false;
|
|
4334
|
+
if (activeFilter === 'tool-fail' && (!r.toolCorrectness || r.toolCorrectness.isCorrect !== false)) return false;
|
|
4335
|
+
if (activeFilter === 'loop-fail' && !r.trajectoryHealth?.hasLoop) return false;
|
|
4336
|
+
if (activeFilter === 'grounding-fail' && (!r.groundedness || r.groundedness.isGrounded !== false)) return false;
|
|
4337
|
+
|
|
4338
|
+
if (searchQuery) {
|
|
4339
|
+
const q = (r.question || '').toLowerCase();
|
|
4340
|
+
const id = (r.id || '').toLowerCase();
|
|
4341
|
+
const cat = (r.category || '').toLowerCase();
|
|
4342
|
+
const ans = (r.answer || '').toLowerCase();
|
|
4343
|
+
if (!q.includes(searchQuery) && !id.includes(searchQuery) && !cat.includes(searchQuery) && !ans.includes(searchQuery)) {
|
|
4344
|
+
return false;
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
return true;
|
|
4348
|
+
});
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
function renderCardsAndTable(rows) {
|
|
4352
|
+
const filtered = getFilteredRows(rows);
|
|
4353
|
+
renderTableRows(filtered);
|
|
4354
|
+
renderMobileCards(filtered);
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
function renderTableRows(filtered) {
|
|
4358
|
+
const tbody = document.getElementById('casesTableBody');
|
|
4359
|
+
if (!filtered.length) {
|
|
4360
|
+
tbody.innerHTML = '<tr><td colspan="9" style="text-align: center; color: var(--text-dim); padding: 36px 0;">No cases matched.</td></tr>';
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
tbody.innerHTML = filtered.map(r => {
|
|
4365
|
+
const isAnomaly = isAnomalyRow(r);
|
|
4366
|
+
const isErr = isErrorRow(r);
|
|
4367
|
+
const anomalyReason = isAnomaly ? getAnomalyReason(r) : '';
|
|
4368
|
+
const anomalyBadge = isAnomaly ? (' <span class="pill pill-anomaly" title="' + escapeHtml(anomalyReason) + '">ANOMALY</span>') : '';
|
|
4369
|
+
const isOk = r.status === 'completed';
|
|
4370
|
+
const cov = r.coverage;
|
|
4371
|
+
const covText = cov ? (cov.matched + '/' + cov.total) : '-';
|
|
4372
|
+
const covPill = cov?.allMatched ? 'pill-success' : (cov?.allMatched === false ? 'pill-error' : 'pill-tag');
|
|
4373
|
+
const judge = r.judgeResult;
|
|
4374
|
+
const judgeHtml = judge ? (' <span class="pill ' + (judge.verdict === 'PASS' ? 'pill-success' : 'pill-error') + '" style="font-size: 10px; margin-left: 4px;">Judge: ' + judge.verdict + '</span>') : '';
|
|
4375
|
+
|
|
4376
|
+
const tool = r.toolCorrectness;
|
|
4377
|
+
let toolPill = 'pill-tag';
|
|
4378
|
+
let toolText = 'n/a';
|
|
4379
|
+
if (tool && !tool.notApplicable) {
|
|
4380
|
+
toolText = 'F1: ' + (tool.f1 ?? 0);
|
|
4381
|
+
toolPill = tool.isCorrect ? 'pill-success' : 'pill-error';
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
const loop = r.trajectoryHealth;
|
|
4385
|
+
const loopPill = loop?.hasLoop ? 'pill-warn' : 'pill-success';
|
|
4386
|
+
const loopText = loop?.hasLoop ? ('Loop (' + loop.loopCount + ')') : 'Optimal';
|
|
4387
|
+
|
|
4388
|
+
const dur = r.durationMs ? ((r.durationMs / 1000).toFixed(2) + 's') : '-';
|
|
4389
|
+
const qEscaped = escapeHtml(r.question || '');
|
|
4390
|
+
|
|
4391
|
+
return '<tr class="case-row" data-case-id="' + r.id + '" onclick="openDrawer(this.dataset.caseId)">' +
|
|
4392
|
+
'<td><span class="pill pill-tag">' + r.id + '</span>' + anomalyBadge + '</td>' +
|
|
4393
|
+
'<td><span style="color: var(--text-muted);">' + (r.category || 'general') + '</span></td>' +
|
|
4394
|
+
'<td style="max-width: 360px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">' + qEscaped + '</td>' +
|
|
4395
|
+
'<td><span class="pill ' + (isOk ? 'pill-success' : (r.status === 'ready' ? 'pill-tag' : 'pill-error')) + '">' + r.status + '</span></td>' +
|
|
4396
|
+
'<td><span class="pill ' + covPill + '">' + covText + '</span>' + judgeHtml + '</td>' +
|
|
4397
|
+
'<td><span class="pill ' + toolPill + '">' + toolText + '</span></td>' +
|
|
4398
|
+
'<td><span class="pill ' + loopPill + '">' + loopText + '</span></td>' +
|
|
4399
|
+
'<td style="font-family: monospace;">' + dur + '</td>' +
|
|
4400
|
+
'<td><button class="btn-run-case ' + (isErr ? 'btn-warning' : '') + '" data-case-id="' + r.id + '" onclick="event.stopPropagation(); runSingleCaseById(this.dataset.caseId)" title="' + (isErr ? ('Retry error: ' + escapeHtml(r.error || r.status)) : 'Run this single case') + '">' + (isErr ? 'Retry' : 'Run') + '</button></td>' +
|
|
4401
|
+
'</tr>';
|
|
4402
|
+
}).join('');
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
function renderMobileCards(filtered) {
|
|
4406
|
+
const container = document.getElementById('mobileCardsContainer');
|
|
4407
|
+
if (!filtered.length) {
|
|
4408
|
+
container.innerHTML = '<div style="text-align: center; color: var(--text-dim); padding: 36px 0; font-size: 13px;">No cases matched.</div>';
|
|
4409
|
+
return;
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
container.innerHTML = filtered.map(r => {
|
|
4413
|
+
const isAnomaly = isAnomalyRow(r);
|
|
4414
|
+
const isErr = isErrorRow(r);
|
|
4415
|
+
const anomalyReason = isAnomaly ? getAnomalyReason(r) : '';
|
|
4416
|
+
const isOk = r.status === 'completed';
|
|
4417
|
+
const cov = r.coverage;
|
|
4418
|
+
const covText = cov ? (cov.matched + '/' + cov.total) : '-';
|
|
4419
|
+
const covPill = cov?.allMatched ? 'pill-success' : (cov?.allMatched === false ? 'pill-error' : 'pill-tag');
|
|
4420
|
+
const dur = r.durationMs ? ((r.durationMs / 1000).toFixed(2) + 's') : '-';
|
|
4421
|
+
const tool = r.toolCorrectness;
|
|
4422
|
+
const toolText = tool && !tool.notApplicable ? ('F1: ' + (tool.f1 ?? 0)) : null;
|
|
4423
|
+
const loop = r.trajectoryHealth;
|
|
4424
|
+
const loopText = loop?.hasLoop ? ('Loop (' + loop.loopCount + ')') : null;
|
|
4425
|
+
const judge = r.judgeResult;
|
|
4426
|
+
const judgeText = judge ? ('Judge: ' + judge.verdict) : null;
|
|
4427
|
+
|
|
4428
|
+
return '<div class="mobile-case-card" data-case-id="' + r.id + '" onclick="openDrawer(this.dataset.caseId)">' +
|
|
4429
|
+
'<div class="mobile-card-top">' +
|
|
4430
|
+
'<div style="display: flex; align-items: center; gap: 6px;">' +
|
|
4431
|
+
'<span class="pill pill-tag" style="font-weight: 700; color: #fff; background: rgba(255,255,255,0.08);">' + r.id + '</span>' +
|
|
4432
|
+
'<span style="font-size: 11px; color: var(--text-muted); text-transform: uppercase; font-weight: 600;">' + (r.category || 'general') + '</span>' +
|
|
4433
|
+
(isAnomaly ? '<span class="pill pill-anomaly" style="font-size: 10px;" title="' + escapeHtml(anomalyReason) + '">ANOMALY</span>' : '') +
|
|
4434
|
+
'</div>' +
|
|
4435
|
+
'<div style="display: flex; align-items: center; gap: 8px;">' +
|
|
4436
|
+
'<span class="pill ' + (isOk ? 'pill-success' : (r.status === 'ready' ? 'pill-tag' : 'pill-error')) + '">' + r.status + '</span>' +
|
|
4437
|
+
'<button class="btn-run-case ' + (isErr ? 'btn-warning' : '') + '" data-case-id="' + r.id + '" onclick="event.stopPropagation(); runSingleCaseById(this.dataset.caseId)" title="' + (isErr ? ('Retry error: ' + escapeHtml(r.error || r.status)) : 'Run this single case') + '">' + (isErr ? 'Retry' : 'Run') + '</button>' +
|
|
4438
|
+
'</div>' +
|
|
4439
|
+
'</div>' +
|
|
4440
|
+
'<div class="mobile-card-question">' + escapeHtml(r.question || '') + '</div>' +
|
|
4441
|
+
'<div class="mobile-card-stats">' +
|
|
4442
|
+
'<span class="pill ' + covPill + '" style="font-size: 10.5px;">Cov: ' + covText + '</span>' +
|
|
4443
|
+
(judgeText ? ('<span class="pill ' + (judge?.verdict === 'PASS' ? 'pill-success' : 'pill-error') + '" style="font-size: 10.5px;">' + judgeText + '</span>') : '') +
|
|
4444
|
+
(toolText ? ('<span class="pill ' + (tool?.isCorrect ? 'pill-success' : 'pill-error') + '" style="font-size: 10.5px;">' + toolText + '</span>') : '') +
|
|
4445
|
+
(loopText ? '<span class="pill pill-warn" style="font-size: 10.5px;">' + loopText + '</span>' : '') +
|
|
4446
|
+
'<span style="font-size: 11px; color: var(--text-dim); margin-left: auto; font-family: monospace;">' + dur + '</span>' +
|
|
4447
|
+
'</div>' +
|
|
4448
|
+
'</div>';
|
|
4449
|
+
}).join('');
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
function openDrawer(caseId) {
|
|
4453
|
+
const row = (currentReport.rows || []).find(r => r.id === caseId);
|
|
4454
|
+
if (!row) return;
|
|
4455
|
+
activeDrawerRow = row;
|
|
4456
|
+
|
|
4457
|
+
document.getElementById('drawerCaseId').textContent = row.id;
|
|
4458
|
+
document.getElementById('drawerTitle').textContent = row.category ? ('[' + row.category + '] ' + row.id) : row.id;
|
|
4459
|
+
|
|
4460
|
+
const isErr = isErrorRow(row);
|
|
4461
|
+
const drawerRunBtn = document.getElementById('drawerRunBtn');
|
|
4462
|
+
if (drawerRunBtn) {
|
|
4463
|
+
if (isErr) {
|
|
4464
|
+
drawerRunBtn.textContent = 'Retry Case';
|
|
4465
|
+
drawerRunBtn.className = 'btn-run-case btn-warning';
|
|
4466
|
+
drawerRunBtn.title = 'Retry failed case (' + escapeHtml(row.error || row.status || 'status: error') + ')';
|
|
4467
|
+
} else {
|
|
4468
|
+
drawerRunBtn.textContent = 'Run Case';
|
|
4469
|
+
drawerRunBtn.className = 'btn-run-case';
|
|
4470
|
+
drawerRunBtn.title = 'Run this single case';
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
const body = document.getElementById('drawerBody');
|
|
4475
|
+
const trace = Array.isArray(row.trace) ? row.trace : [];
|
|
4476
|
+
|
|
4477
|
+
const totalMs = row.durationMs || 1;
|
|
4478
|
+
const llmMs = row.latencyBreakdown?.llmMs || Math.round(totalMs * 0.7);
|
|
4479
|
+
const toolMs = row.latencyBreakdown?.toolMs || (totalMs > llmMs ? totalMs - llmMs : 0);
|
|
4480
|
+
const netMs = Math.max(0, totalMs - llmMs - toolMs);
|
|
4481
|
+
const llmPct = Math.min(100, Math.round((llmMs / totalMs) * 100));
|
|
4482
|
+
const toolPct = Math.min(100 - llmPct, Math.round((toolMs / totalMs) * 100));
|
|
4483
|
+
const netPct = Math.max(0, 100 - llmPct - toolPct);
|
|
4484
|
+
|
|
4485
|
+
const promptTok = row.metrics?.counts?.prompt_tokens ?? row.metrics?.promptTokens ?? row.telemetry?.promptTokens ?? '-';
|
|
4486
|
+
const compTok = row.metrics?.counts?.completion_tokens ?? row.latencyBreakdown?.outputTokens ?? row.metrics?.completionTokens ?? row.telemetry?.completionTokens ?? '-';
|
|
4487
|
+
const decodeTps = row.latencyBreakdown?.decodeTps ? Number(row.latencyBreakdown.decodeTps).toFixed(1) : (row.metrics?.rates?.decode_tps ? Number(row.metrics.rates.decode_tps).toFixed(1) : (row.metrics?.decodeTps ? Number(row.metrics.decodeTps).toFixed(1) : (row.telemetry?.generationTokensPerSecond ? Number(row.telemetry.generationTokensPerSecond).toFixed(1) : '-')));
|
|
4488
|
+
const isLocal = !currentReport?.metadata?.baseUrl || currentReport.metadata.baseUrl.includes('127.0.0.1') || currentReport.metadata.baseUrl.includes('localhost') || (currentReport?.metadata?.model || '').includes('gemma-4') || (currentReport?.metadata?.model || '').includes('lmstudio');
|
|
4489
|
+
const estCost = ((Number(promptTok) || 0) * 0.0000005 + (Number(compTok) || 0) * 0.0000015).toFixed(4);
|
|
4490
|
+
const costValueDisplay = isLocal ? '$0.00' : ('$' + estCost);
|
|
4491
|
+
const costLabelDisplay = isLocal ? 'API Cost (Free)' : 'Est. Cost';
|
|
4492
|
+
const costTitleTooltip = isLocal ? ('Local LM Studio inference is $0.00. Cloud equivalent: $' + estCost) : ('Estimated Cloud API Cost: $' + estCost);
|
|
4493
|
+
|
|
4494
|
+
let anomalySectionHtml = '';
|
|
4495
|
+
if (isAnomalyRow(row)) {
|
|
4496
|
+
const reason = getAnomalyReason(row);
|
|
4497
|
+
anomalySectionHtml = '<div class="detail-section">' +
|
|
4498
|
+
'<div class="detail-label" style="color: ' + (isErr ? 'var(--accent-red)' : 'var(--accent-amber)') + ';">' + (isErr ? 'Status Error Diagnosis' : 'Anomaly Diagnosis') + '</div>' +
|
|
4499
|
+
'<div style="background: ' + (isErr ? 'rgba(239,68,68,0.08)' : 'rgba(251,191,36,0.08)') + '; border: 1px solid ' + (isErr ? 'rgba(239,68,68,0.3)' : 'rgba(251,191,36,0.3)') + '; border-radius: 6px; padding: 10px 12px; font-size: 12px;">' +
|
|
4500
|
+
'<div style="font-weight: 700; color: ' + (isErr ? '#ef4444' : '#fbbf24') + '; margin-bottom: 4px;">Detected: ' + escapeHtml(reason) + '</div>' +
|
|
4501
|
+
'<div style="color: var(--text-muted); font-size: 11.5px; line-height: 1.4;">' + (isErr ? 'This case encountered an execution error or timeout. Click "Retry Case" to re-execute.' : 'This case deviated from normal benchmark acceptance gates.') + '</div>' +
|
|
4502
|
+
'</div>' +
|
|
4503
|
+
'</div>';
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4506
|
+
let judgeSectionHtml = '';
|
|
4507
|
+
if (row.judgeResult) {
|
|
4508
|
+
const jr = row.judgeResult;
|
|
4509
|
+
const isPass = jr.verdict === 'PASS';
|
|
4510
|
+
const color = isPass ? 'var(--accent-emerald)' : 'var(--accent-red)';
|
|
4511
|
+
judgeSectionHtml = '<div class="detail-section">' +
|
|
4512
|
+
'<div class="detail-label">LLM-as-a-Judge Assessment</div>' +
|
|
4513
|
+
'<div style="background: rgba(167,139,250,0.06); border: 1px solid rgba(167,139,250,0.2); border-radius: 6px; padding: 10px 12px; font-size: 12px;">' +
|
|
4514
|
+
'<div style="display: flex; gap: 12px; font-weight: 600; margin-bottom: 6px; align-items: center;">' +
|
|
4515
|
+
'<span style="color: ' + color + '; font-weight: 800; font-size: 13px;">' + (jr.verdict || '-') + ' (' + (jr.score ?? '-') + ')</span>' +
|
|
4516
|
+
(jr.latencyMs ? '<span style="color: var(--text-dim); font-size: 11px;">Latency: ' + jr.latencyMs + 'ms</span>' : '') +
|
|
4517
|
+
(jr.evaluatorModel ? '<span style="color: var(--text-dim); font-size: 11px;">Model: ' + escapeHtml(jr.evaluatorModel) + '</span>' : '') +
|
|
4518
|
+
'</div>' +
|
|
4519
|
+
'<div style="color: var(--text-muted); line-height: 1.4; white-space: pre-wrap;">' + escapeHtml(jr.reasoning || jr.error || 'No reasoning provided.') + '</div>' +
|
|
4520
|
+
'</div>' +
|
|
4521
|
+
'</div>';
|
|
4522
|
+
} else if (row.judgment) {
|
|
4523
|
+
judgeSectionHtml = '<div class="detail-section">' +
|
|
4524
|
+
'<div class="detail-label">LLM Judge Assessment</div>' +
|
|
4525
|
+
'<div style="background: rgba(167,139,250,0.06); border: 1px solid rgba(167,139,250,0.2); border-radius: 6px; padding: 10px 12px; font-size: 12px;">' +
|
|
4526
|
+
'<div style="display: flex; gap: 12px; font-weight: 600; margin-bottom: 6px;">' +
|
|
4527
|
+
'<span style="color: var(--accent-rose);">Correctness: ' + (row.judgment.correctness ?? '-') + '/4</span>' +
|
|
4528
|
+
'<span style="color: var(--accent-violet);">Grounding: ' + (row.judgment.grounding ?? '-') + '/4</span>' +
|
|
4529
|
+
'<span style="color: var(--accent-cyan);">Instruction: ' + (row.judgment.instructionFollowing ?? '-') + '/4</span>' +
|
|
4530
|
+
'</div>' +
|
|
4531
|
+
'<div style="color: var(--text-muted); line-height: 1.4;">' + escapeHtml(row.judgment.reason || 'No judgment reason provided.') + '</div>' +
|
|
4532
|
+
'</div>' +
|
|
4533
|
+
'</div>';
|
|
4534
|
+
}
|
|
4535
|
+
|
|
4536
|
+
let timelineHtml = '<div style="color: var(--text-dim); font-size: 12px;">No trace events recorded.</div>';
|
|
4537
|
+
if (trace.length > 0) {
|
|
4538
|
+
timelineHtml = '<div class="react-timeline">' + trace.map(t => {
|
|
4539
|
+
const isResult = t.event === 'result';
|
|
4540
|
+
const title = isResult ? ('Observation: ' + t.name + ' (' + (t.durationMs || 0) + 'ms)') : ('Action: ' + t.name);
|
|
4541
|
+
const payload = isResult ? t.value : t.parameters;
|
|
4542
|
+
const jsonStr = payload ? JSON.stringify(payload, null, 2) : '';
|
|
4543
|
+
return '<div class="timeline-step">' +
|
|
4544
|
+
'<div style="font-weight: 600; font-size: 12px; color: ' + (isResult ? 'var(--accent-emerald)' : 'var(--accent-cyan)') + ';">' + escapeHtml(title) + '</div>' +
|
|
4545
|
+
(jsonStr ? '<pre class="code-block">' + escapeHtml(jsonStr) + '</pre>' : '') +
|
|
4546
|
+
'</div>';
|
|
4547
|
+
}).join('') + '</div>';
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
const groundText = row.groundedness?.notApplicable ? 'N/A' : (row.groundedness?.isGrounded ? 'Yes' : (row.groundedness ? 'No' : '-'));
|
|
4551
|
+
|
|
4552
|
+
body.innerHTML = anomalySectionHtml +
|
|
4553
|
+
'<div class="detail-section">' +
|
|
4554
|
+
'<div class="detail-section-header">' +
|
|
4555
|
+
'<div class="detail-label">Question / Prompt</div>' +
|
|
4556
|
+
'<button class="copy-btn" data-field="question" onclick="copyActiveDrawerField(this.dataset.field)">Copy</button>' +
|
|
4557
|
+
'</div>' +
|
|
4558
|
+
'<div class="detail-content" style="font-weight: 500;">' + escapeHtml(row.question || '') + '</div>' +
|
|
4559
|
+
'</div>' +
|
|
4560
|
+
'<div class="detail-section">' +
|
|
4561
|
+
'<div class="detail-section-header">' +
|
|
4562
|
+
'<div class="detail-label">Agent Final Answer</div>' +
|
|
4563
|
+
'<button class="copy-btn" data-field="answer" onclick="copyActiveDrawerField(this.dataset.field)">Copy</button>' +
|
|
4564
|
+
'</div>' +
|
|
4565
|
+
'<div class="detail-content" style="white-space: pre-wrap; background: rgba(0,0,0,0.3); padding: 12px; border-radius: 6px; font-family: inherit;">' + escapeHtml(row.answer || row.error || 'No answer generated.') + '</div>' +
|
|
4566
|
+
'</div>' +
|
|
4567
|
+
'<div class="detail-section">' +
|
|
4568
|
+
'<div class="detail-label">Trace Waterfall & Latency Breakdown</div>' +
|
|
4569
|
+
'<div class="waterfall-container">' +
|
|
4570
|
+
'<div class="waterfall-bar">' +
|
|
4571
|
+
'<div class="waterfall-seg seg-llm" style="width: ' + llmPct + '%;" title="Model: ' + llmMs + 'ms"></div>' +
|
|
4572
|
+
'<div class="waterfall-seg seg-tool" style="width: ' + toolPct + '%;" title="Tools: ' + toolMs + 'ms"></div>' +
|
|
4573
|
+
'<div class="waterfall-seg seg-net" style="width: ' + netPct + '%;" title="Overhead: ' + netMs + 'ms"></div>' +
|
|
4574
|
+
'</div>' +
|
|
4575
|
+
'<div class="waterfall-legend">' +
|
|
4576
|
+
'<span><span class="legend-dot seg-llm"></span> Model: ' + llmMs + 'ms (' + llmPct + '%)</span>' +
|
|
4577
|
+
'<span><span class="legend-dot seg-tool"></span> Tools: ' + toolMs + 'ms (' + toolPct + '%)</span>' +
|
|
4578
|
+
'<span><span class="legend-dot seg-net"></span> Net: ' + netMs + 'ms</span>' +
|
|
4579
|
+
'</div>' +
|
|
4580
|
+
'</div>' +
|
|
4581
|
+
'</div>' +
|
|
4582
|
+
'<div class="detail-section">' +
|
|
4583
|
+
'<div class="detail-label">Token Cost & Throughput Profiler</div>' +
|
|
4584
|
+
'<div class="token-grid">' +
|
|
4585
|
+
'<div class="token-stat-box"><span class="token-num">' + promptTok + '</span><span class="token-lbl">Prompt Tokens</span></div>' +
|
|
4586
|
+
'<div class="token-stat-box"><span class="token-num">' + compTok + '</span><span class="token-lbl">Completion Tokens</span></div>' +
|
|
4587
|
+
'<div class="token-stat-box"><span class="token-num">' + decodeTps + '</span><span class="token-lbl">Decode TPS</span></div>' +
|
|
4588
|
+
'<div class="token-stat-box" title="' + costTitleTooltip + '"><span class="token-num" style="' + (isLocal ? 'color: var(--accent-emerald);' : '') + '">' + costValueDisplay + '</span><span class="token-lbl">' + costLabelDisplay + '</span></div>' +
|
|
4589
|
+
'</div>' +
|
|
4590
|
+
'</div>' +
|
|
4591
|
+
judgeSectionHtml +
|
|
4592
|
+
'<div class="detail-section">' +
|
|
4593
|
+
'<div class="detail-label">ReAct Execution Trace (' + trace.length + ' events)</div>' +
|
|
4594
|
+
timelineHtml +
|
|
4595
|
+
'</div>' +
|
|
4596
|
+
'<div class="detail-section">' +
|
|
4597
|
+
'<div class="detail-label">Metrics & Analysis</div>' +
|
|
4598
|
+
'<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 12px;">' +
|
|
4599
|
+
'<div style="background: rgba(255,255,255,0.04); padding: 8px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.05);">Coverage: ' + (row.coverage?.matched ?? '-') + '/' + (row.coverage?.total ?? '-') + '</div>' +
|
|
4600
|
+
'<div style="background: rgba(255,255,255,0.04); padding: 8px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.05);">Grounded: ' + groundText + '</div>' +
|
|
4601
|
+
'<div style="background: rgba(255,255,255,0.04); padding: 8px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.05);">Duration: ' + (row.durationMs || 0) + 'ms</div>' +
|
|
4602
|
+
'<div style="background: rgba(255,255,255,0.04); padding: 8px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.05);">Loops: ' + (row.trajectoryHealth?.hasLoop ? 'Detected' : 'Optimal') + '</div>' +
|
|
4603
|
+
'</div>' +
|
|
4604
|
+
'</div>';
|
|
4605
|
+
|
|
4606
|
+
document.getElementById('drawerOverlay').classList.add('open');
|
|
4607
|
+
document.getElementById('detailDrawer').classList.add('open');
|
|
4608
|
+
document.body.style.overflow = 'hidden';
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
function closeDrawer() {
|
|
4612
|
+
document.getElementById('drawerOverlay').classList.remove('open');
|
|
4613
|
+
document.getElementById('detailDrawer').classList.remove('open');
|
|
4614
|
+
document.body.style.overflow = '';
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4617
|
+
function copyText(str) {
|
|
4618
|
+
if (!str) return;
|
|
4619
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
4620
|
+
navigator.clipboard.writeText(str).then(() => {
|
|
4621
|
+
showSyncNotice('Copied to clipboard');
|
|
4622
|
+
}).catch(() => {
|
|
4623
|
+
fallbackCopy(str);
|
|
4624
|
+
});
|
|
4625
|
+
} else {
|
|
4626
|
+
fallbackCopy(str);
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
function fallbackCopy(str) {
|
|
4631
|
+
try {
|
|
4632
|
+
const el = document.createElement('textarea');
|
|
4633
|
+
el.value = str;
|
|
4634
|
+
el.setAttribute('readonly', '');
|
|
4635
|
+
el.style.position = 'absolute';
|
|
4636
|
+
el.style.left = '-9999px';
|
|
4637
|
+
document.body.appendChild(el);
|
|
4638
|
+
el.select();
|
|
4639
|
+
document.execCommand('copy');
|
|
4640
|
+
document.body.removeChild(el);
|
|
4641
|
+
showSyncNotice('Copied to clipboard');
|
|
4642
|
+
} catch {}
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4645
|
+
function escapeHtml(str) {
|
|
4646
|
+
return String(str || '')
|
|
4647
|
+
.replace(/&/g, '&')
|
|
4648
|
+
.replace(/</g, '<')
|
|
4649
|
+
.replace(/>/g, '>')
|
|
4650
|
+
.replace(/"/g, '"')
|
|
4651
|
+
.replace(/'/g, ''');
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4654
|
+
async function loadAvailableCases() {
|
|
4655
|
+
try {
|
|
4656
|
+
const res = await fetch('/api/eval/cases');
|
|
4657
|
+
if (res.ok) {
|
|
4658
|
+
window.allAvailableCases = await res.json();
|
|
4659
|
+
const list = window.allAvailableCases || [];
|
|
4660
|
+
const sel = document.getElementById('caseSelect');
|
|
4661
|
+
if (sel && list.length) {
|
|
4662
|
+
sel.innerHTML = list.map(c =>
|
|
4663
|
+
'<option value="' + c.id + '">' + c.id + ' [' + c.category + (c.dimension ? (' / ' + c.dimension) : '') + '] ' + escapeHtml(c.question).slice(0, 50) + '...</option>'
|
|
4664
|
+
).join('');
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
try {
|
|
4668
|
+
const sRes = await fetch('/api/eval/suites');
|
|
4669
|
+
if (sRes.ok) {
|
|
4670
|
+
const suites = await sRes.json();
|
|
4671
|
+
const sSel = document.getElementById('suiteSelect');
|
|
4672
|
+
if (sSel) {
|
|
4673
|
+
const cur = sSel.value;
|
|
4674
|
+
sSel.innerHTML =
|
|
4675
|
+
'<option value="single">Single Case (1 ข้อเฉพาะ)</option>' +
|
|
4676
|
+
'<option value="quick">Quick Smoke (3 ข้อ)</option>' +
|
|
4677
|
+
'<option value="catalog">Catalog QA (' + suites.catalog.count + ' ข้อ)</option>' +
|
|
4678
|
+
(suites.expanded.count > 0 ? ('<option value="expanded">New Cases (' + suites.expanded.count + ' ข้อ)</option>') : '') +
|
|
4679
|
+
'<option value="security">Security (' + suites.security.count + ' ข้อ)</option>' +
|
|
4680
|
+
'<option value="legacy">Legacy Case IDs (' + suites.legacy.count + ' ข้อ)</option>' +
|
|
4681
|
+
'<option value="all">All Comprehensive (' + suites.all.count + ' ข้อ)</option>';
|
|
4682
|
+
sSel.value = cur;
|
|
4683
|
+
}
|
|
4684
|
+
const chips = document.querySelector('.preset-chips');
|
|
4685
|
+
if (chips) {
|
|
4686
|
+
chips.innerHTML =
|
|
4687
|
+
'<span style="font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase;">Presets:</span>' +
|
|
4688
|
+
'<button class="preset-chip" onclick="applyPreset(\\'single\\')">Single Case (1 ข้อ)</button>' +
|
|
4689
|
+
'<button class="preset-chip" onclick="applyPreset(\\'quick\\')">Quick Smoke (3)</button>' +
|
|
4690
|
+
'<button class="preset-chip" onclick="applyPreset(\\'catalog\\')">Catalog QA (' + suites.catalog.count + ')</button>' +
|
|
4691
|
+
(suites.expanded.count > 0 ? ('<button class="preset-chip" onclick="applyPreset(\\'expanded\\')">Expanded (' + suites.expanded.count + ')</button>') : '') +
|
|
4692
|
+
'<button class="preset-chip" onclick="applyPreset(\\'security\\')">Security (' + suites.security.count + ')</button>' +
|
|
4693
|
+
'<button class="preset-chip" onclick="applyPreset(\\'legacy\\')">Legacy (' + suites.legacy.count + ')</button>' +
|
|
4694
|
+
'<button class="preset-chip" onclick="applyPreset(\\'all\\')">All Comprehensive (' + suites.all.count + ')</button>';
|
|
4695
|
+
syncPresetChips();
|
|
4696
|
+
}
|
|
4697
|
+
const lInput = document.getElementById('limitInput');
|
|
4698
|
+
if (lInput) lInput.setAttribute('max', String(suites.total));
|
|
4699
|
+
}
|
|
4700
|
+
} catch {}
|
|
4701
|
+
|
|
4702
|
+
const hasRealData = Array.isArray(currentReport.rows) && currentReport.rows.some(r => r.status && r.status !== 'ready');
|
|
4703
|
+
if (!hasRealData && (!currentReport.rows || currentReport.rows.length === 0)) {
|
|
4704
|
+
currentReport.rows = list.map(c => ({
|
|
4705
|
+
id: c.id,
|
|
4706
|
+
category: c.category,
|
|
4707
|
+
question: c.question,
|
|
4708
|
+
status: 'ready',
|
|
4709
|
+
coverage: null,
|
|
4710
|
+
toolCorrectness: null,
|
|
4711
|
+
trajectoryHealth: null,
|
|
4712
|
+
durationMs: null
|
|
4713
|
+
}));
|
|
4714
|
+
renderDashboard();
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
} catch (err) {}
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
function initDrawerGesture() {
|
|
4721
|
+
const drawer = document.getElementById('detailDrawer');
|
|
4722
|
+
if (!drawer) return;
|
|
4723
|
+
let startY = 0;
|
|
4724
|
+
let currentY = 0;
|
|
4725
|
+
let isDragging = false;
|
|
4726
|
+
|
|
4727
|
+
drawer.addEventListener('touchstart', e => {
|
|
4728
|
+
const bodyEl = document.getElementById('drawerBody');
|
|
4729
|
+
if (!bodyEl || bodyEl.scrollTop <= 0) {
|
|
4730
|
+
startY = e.touches[0].clientY;
|
|
4731
|
+
currentY = startY;
|
|
4732
|
+
isDragging = true;
|
|
4733
|
+
} else {
|
|
4734
|
+
isDragging = false;
|
|
4735
|
+
}
|
|
4736
|
+
}, { passive: true });
|
|
4737
|
+
|
|
4738
|
+
drawer.addEventListener('touchmove', e => {
|
|
4739
|
+
if (!isDragging) return;
|
|
4740
|
+
currentY = e.touches[0].clientY;
|
|
4741
|
+
const delta = currentY - startY;
|
|
4742
|
+
if (delta > 0 && window.innerWidth < 768) {
|
|
4743
|
+
drawer.style.transform = 'translateY(' + delta + 'px)';
|
|
4744
|
+
}
|
|
4745
|
+
}, { passive: true });
|
|
4746
|
+
|
|
4747
|
+
drawer.addEventListener('touchend', () => {
|
|
4748
|
+
if (!isDragging) return;
|
|
4749
|
+
const delta = currentY - startY;
|
|
4750
|
+
drawer.style.transform = '';
|
|
4751
|
+
if (delta > 85 && window.innerWidth < 768) {
|
|
4752
|
+
closeDrawer();
|
|
4753
|
+
}
|
|
4754
|
+
isDragging = false;
|
|
4755
|
+
});
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4758
|
+
function bootEvalStudio() {
|
|
4759
|
+
restoreConfigFromStorage();
|
|
4760
|
+
restoreReportFromStorage();
|
|
4761
|
+
loadLatestReport();
|
|
4762
|
+
initSSE();
|
|
4763
|
+
initLmsStream();
|
|
4764
|
+
loadAvailableCases().then(() => {
|
|
4765
|
+
restoreConfigFromStorage();
|
|
4766
|
+
});
|
|
4767
|
+
checkServerStatus();
|
|
4768
|
+
initDrawerGesture();
|
|
4769
|
+
setInterval(() => {
|
|
4770
|
+
if (!isEvaluationRunning) {
|
|
4771
|
+
fetch('/api/eval/report').then(r => r.ok ? r.json() : null).then(rep => {
|
|
4772
|
+
if (rep && Array.isArray(rep.rows) && rep.rows.length > 0 && rep.rows.length !== (currentReport.rows?.length || 0)) {
|
|
4773
|
+
currentReport = rep;
|
|
4774
|
+
saveReportToStorage(currentReport);
|
|
4775
|
+
renderDashboard();
|
|
4776
|
+
const total = rep.metadata?.caseIds?.length || ${totalCount};
|
|
4777
|
+
document.getElementById('progressHero').style.display = 'flex';
|
|
4778
|
+
document.getElementById('runTitle').textContent = 'Live Synced (' + currentReport.rows.length + ' cases)';
|
|
4779
|
+
updateProgress(currentReport.rows.length, total);
|
|
4780
|
+
showSyncNotice('Live synced: ' + rep.rows.length + ' cases');
|
|
4781
|
+
}
|
|
4782
|
+
}).catch(() => {});
|
|
4783
|
+
}
|
|
4784
|
+
}, 5000);
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
if (document.readyState === 'loading') {
|
|
4788
|
+
window.addEventListener('DOMContentLoaded', bootEvalStudio);
|
|
4789
|
+
} else {
|
|
4790
|
+
bootEvalStudio();
|
|
4791
|
+
}
|
|
4792
|
+
</script>
|
|
4793
|
+
<div id="syncToast" class="sync-toast"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg> <span>Synced</span></div>
|
|
4794
|
+
</body>
|
|
4795
|
+
</html>`;
|
|
4796
|
+
}
|