Spaces:
Running
Running
feat: mobile responsive layouts, SPA direct routing rewrites, and Config 1/2/3 hover tooltips
Browse files- frontend/src/App.jsx +21 -13
- frontend/src/components/OpeningLanding.jsx +82 -60
- frontend/src/docpilot/pages/Dashboard.jsx +18 -4
- frontend/src/gaugepilot/components/ConfigBadge.jsx +211 -0
- frontend/src/gaugepilot/components/CustomSelector.jsx +2 -1
- frontend/src/gaugepilot/components/leaderboards/OverallTable.jsx +27 -22
- frontend/src/gaugepilot/components/visualizations/HeatmapView.jsx +42 -135
- frontend/src/gaugepilot/components/visualizations/ParallelCoordinatesView.jsx +85 -197
- frontend/src/gaugepilot/components/visualizations/ParetoView.jsx +41 -20
- frontend/src/gaugepilot/components/visualizations/PerformanceProfile.jsx +28 -10
- frontend/src/gaugepilot/components/visualizations/RadarView.jsx +92 -255
- frontend/src/gaugepilot/components/visualizations/RankingBarView.jsx +28 -12
- frontend/src/gaugepilot/components/visualizations/ScatterView.jsx +28 -9
- frontend/src/gaugepilot/hooks/useVisualizations.js +14 -20
- frontend/src/gaugepilot/pages/Aianalysis.jsx +1 -1
- frontend/src/gaugepilot/pages/ExperimentSetup.jsx +4 -3
- frontend/src/gaugepilot/pages/Leaderboards.jsx +28 -21
- frontend/src/gaugepilot/utils/configUtils.js +166 -0
- frontend/src/main.jsx +179 -168
- frontend/src/tracepilot/TraceExplorer.jsx +1 -1
- frontend/vercel.json +8 -0
- vercel.json +8 -0
frontend/src/App.jsx
CHANGED
|
@@ -250,25 +250,29 @@ function PilotMasterHome({ username, plan, onOpen, onLogout }) {
|
|
| 250 |
return (
|
| 251 |
<div
|
| 252 |
key={experimentMode ? "exp" : "prod"}
|
|
|
|
| 253 |
style={{
|
| 254 |
background: "var(--bg-primary)",
|
| 255 |
color: "var(--text-primary)",
|
| 256 |
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
| 257 |
width: "100vw",
|
| 258 |
-
|
| 259 |
boxSizing: "border-box",
|
| 260 |
display: "grid",
|
| 261 |
gridTemplateRows: "auto 1fr auto",
|
| 262 |
-
|
| 263 |
}}
|
| 264 |
>
|
| 265 |
{/* TOP BAR */}
|
| 266 |
<header
|
|
|
|
| 267 |
style={{
|
| 268 |
display: "flex",
|
| 269 |
justifyContent: "space-between",
|
| 270 |
alignItems: "center",
|
| 271 |
padding: "20px 48px",
|
|
|
|
|
|
|
| 272 |
}}
|
| 273 |
>
|
| 274 |
<div>
|
|
@@ -288,7 +292,7 @@ function PilotMasterHome({ username, plan, onOpen, onLogout }) {
|
|
| 288 |
</p>
|
| 289 |
</div>
|
| 290 |
|
| 291 |
-
<div style={{ display: "flex", alignItems: "center", gap: "
|
| 292 |
|
| 293 |
<div
|
| 294 |
style={{
|
|
@@ -363,7 +367,7 @@ function PilotMasterHome({ username, plan, onOpen, onLogout }) {
|
|
| 363 |
</header>
|
| 364 |
|
| 365 |
{/* CENTER */}
|
| 366 |
-
<div style={{ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", gap: "
|
| 367 |
<div style={{ textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center", gap: "10px" }}>
|
| 368 |
<button
|
| 369 |
onClick={toggleMode}
|
|
@@ -388,7 +392,7 @@ function PilotMasterHome({ username, plan, onOpen, onLogout }) {
|
|
| 388 |
<h2
|
| 389 |
style={{
|
| 390 |
margin: "12px 0 0",
|
| 391 |
-
fontSize: "36px",
|
| 392 |
fontWeight: "700",
|
| 393 |
letterSpacing: "-1px",
|
| 394 |
color: "#ffffff",
|
|
@@ -403,7 +407,7 @@ function PilotMasterHome({ username, plan, onOpen, onLogout }) {
|
|
| 403 |
</p>
|
| 404 |
</div>
|
| 405 |
|
| 406 |
-
<div style={{ display: "flex", gap: "20px", flexWrap: "wrap", justifyContent: "center" }}>
|
| 407 |
<ProductCard
|
| 408 |
name="DocPilot"
|
| 409 |
description="Upload multi-format documents, ask complex questions, and receive grounded answers with citations."
|
|
@@ -435,7 +439,7 @@ function PilotMasterHome({ username, plan, onOpen, onLogout }) {
|
|
| 435 |
</div>
|
| 436 |
|
| 437 |
{/* FOOTER */}
|
| 438 |
-
<footer style={{ padding: "18px 48px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 439 |
<p style={{ margin: 0, fontSize: "12px", color: "var(--text-muted)" }}>
|
| 440 |
PilotMaster · Powered by PilotCore Kernel
|
| 441 |
</p>
|
|
@@ -466,8 +470,9 @@ function ProductCard({ name, icon, description, tags, href, onClick, accent }) {
|
|
| 466 |
onMouseEnter={() => setHovered(true)}
|
| 467 |
onMouseLeave={() => setHovered(false)}
|
| 468 |
style={{
|
| 469 |
-
width: "
|
| 470 |
-
|
|
|
|
| 471 |
borderRadius: "24px",
|
| 472 |
cursor: "pointer",
|
| 473 |
textDecoration: "none",
|
|
@@ -484,7 +489,7 @@ function ProductCard({ name, icon, description, tags, href, onClick, accent }) {
|
|
| 484 |
>
|
| 485 |
|
| 486 |
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 487 |
-
<h3 style={{ margin: 0, fontSize: "
|
| 488 |
{name}
|
| 489 |
</h3>
|
| 490 |
<span style={{ fontSize: "18px", color: hovered ? "#ffffff" : accent, transition: "color 0.15s" }}>
|
|
@@ -756,26 +761,29 @@ function ForgotPassword({ goBack }) {
|
|
| 756 |
function AuthShell({ children }) {
|
| 757 |
return (
|
| 758 |
<div
|
|
|
|
| 759 |
style={{
|
| 760 |
background: "var(--bg-primary)",
|
| 761 |
width: "100vw",
|
| 762 |
-
|
| 763 |
display: "flex",
|
| 764 |
justifyContent: "center",
|
| 765 |
alignItems: "center",
|
| 766 |
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
| 767 |
boxSizing: "border-box",
|
| 768 |
-
padding: "24px",
|
|
|
|
| 769 |
}}
|
| 770 |
>
|
| 771 |
<div
|
|
|
|
| 772 |
style={{
|
| 773 |
width: "100%",
|
| 774 |
maxWidth: "420px",
|
| 775 |
borderRadius: "28px",
|
| 776 |
background: "rgba(255, 255, 255, 0.03)",
|
| 777 |
boxShadow: "0 24px 60px rgba(0, 0, 0, 0.4)",
|
| 778 |
-
padding: "36px 32px",
|
| 779 |
display: "flex",
|
| 780 |
flexDirection: "column",
|
| 781 |
boxSizing: "border-box",
|
|
|
|
| 250 |
return (
|
| 251 |
<div
|
| 252 |
key={experimentMode ? "exp" : "prod"}
|
| 253 |
+
className="pilot-home"
|
| 254 |
style={{
|
| 255 |
background: "var(--bg-primary)",
|
| 256 |
color: "var(--text-primary)",
|
| 257 |
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
| 258 |
width: "100vw",
|
| 259 |
+
minHeight: "100dvh",
|
| 260 |
boxSizing: "border-box",
|
| 261 |
display: "grid",
|
| 262 |
gridTemplateRows: "auto 1fr auto",
|
| 263 |
+
overflowY: "auto",
|
| 264 |
}}
|
| 265 |
>
|
| 266 |
{/* TOP BAR */}
|
| 267 |
<header
|
| 268 |
+
className="pilot-home-topbar"
|
| 269 |
style={{
|
| 270 |
display: "flex",
|
| 271 |
justifyContent: "space-between",
|
| 272 |
alignItems: "center",
|
| 273 |
padding: "20px 48px",
|
| 274 |
+
flexWrap: "wrap",
|
| 275 |
+
gap: "14px",
|
| 276 |
}}
|
| 277 |
>
|
| 278 |
<div>
|
|
|
|
| 292 |
</p>
|
| 293 |
</div>
|
| 294 |
|
| 295 |
+
<div className="pilot-home-actions" style={{ display: "flex", alignItems: "center", gap: "10px", flexWrap: "wrap" }}>
|
| 296 |
|
| 297 |
<div
|
| 298 |
style={{
|
|
|
|
| 367 |
</header>
|
| 368 |
|
| 369 |
{/* CENTER */}
|
| 370 |
+
<div className="pilot-home-center" style={{ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", gap: "28px", padding: "20px 24px" }}>
|
| 371 |
<div style={{ textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center", gap: "10px" }}>
|
| 372 |
<button
|
| 373 |
onClick={toggleMode}
|
|
|
|
| 392 |
<h2
|
| 393 |
style={{
|
| 394 |
margin: "12px 0 0",
|
| 395 |
+
fontSize: "clamp(26px, 4vw, 36px)",
|
| 396 |
fontWeight: "700",
|
| 397 |
letterSpacing: "-1px",
|
| 398 |
color: "#ffffff",
|
|
|
|
| 407 |
</p>
|
| 408 |
</div>
|
| 409 |
|
| 410 |
+
<div className="pilot-home-grid" style={{ display: "flex", gap: "20px", flexWrap: "wrap", justifyContent: "center", width: "100%", maxWidth: "1100px" }}>
|
| 411 |
<ProductCard
|
| 412 |
name="DocPilot"
|
| 413 |
description="Upload multi-format documents, ask complex questions, and receive grounded answers with citations."
|
|
|
|
| 439 |
</div>
|
| 440 |
|
| 441 |
{/* FOOTER */}
|
| 442 |
+
<footer className="pilot-home-footer" style={{ padding: "18px 48px", display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: "10px" }}>
|
| 443 |
<p style={{ margin: 0, fontSize: "12px", color: "var(--text-muted)" }}>
|
| 444 |
PilotMaster · Powered by PilotCore Kernel
|
| 445 |
</p>
|
|
|
|
| 470 |
onMouseEnter={() => setHovered(true)}
|
| 471 |
onMouseLeave={() => setHovered(false)}
|
| 472 |
style={{
|
| 473 |
+
width: "100%",
|
| 474 |
+
maxWidth: "340px",
|
| 475 |
+
padding: "26px 22px",
|
| 476 |
borderRadius: "24px",
|
| 477 |
cursor: "pointer",
|
| 478 |
textDecoration: "none",
|
|
|
|
| 489 |
>
|
| 490 |
|
| 491 |
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 492 |
+
<h3 style={{ margin: 0, fontSize: "22px", fontWeight: "800", color: accent, letterSpacing: "-0.6px", transition: "color 0.15s ease" }}>
|
| 493 |
{name}
|
| 494 |
</h3>
|
| 495 |
<span style={{ fontSize: "18px", color: hovered ? "#ffffff" : accent, transition: "color 0.15s" }}>
|
|
|
|
| 761 |
function AuthShell({ children }) {
|
| 762 |
return (
|
| 763 |
<div
|
| 764 |
+
className="auth-shell"
|
| 765 |
style={{
|
| 766 |
background: "var(--bg-primary)",
|
| 767 |
width: "100vw",
|
| 768 |
+
minHeight: "100dvh",
|
| 769 |
display: "flex",
|
| 770 |
justifyContent: "center",
|
| 771 |
alignItems: "center",
|
| 772 |
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
| 773 |
boxSizing: "border-box",
|
| 774 |
+
padding: "clamp(16px, 4vw, 24px)",
|
| 775 |
+
overflowY: "auto",
|
| 776 |
}}
|
| 777 |
>
|
| 778 |
<div
|
| 779 |
+
className="auth-panel"
|
| 780 |
style={{
|
| 781 |
width: "100%",
|
| 782 |
maxWidth: "420px",
|
| 783 |
borderRadius: "28px",
|
| 784 |
background: "rgba(255, 255, 255, 0.03)",
|
| 785 |
boxShadow: "0 24px 60px rgba(0, 0, 0, 0.4)",
|
| 786 |
+
padding: "clamp(24px, 5vw, 36px) clamp(20px, 4vw, 32px)",
|
| 787 |
display: "flex",
|
| 788 |
flexDirection: "column",
|
| 789 |
boxSizing: "border-box",
|
frontend/src/components/OpeningLanding.jsx
CHANGED
|
@@ -232,10 +232,12 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 232 |
<header
|
| 233 |
style={{
|
| 234 |
width: "100%",
|
| 235 |
-
padding: "16px 36px",
|
| 236 |
display: "flex",
|
| 237 |
justifyContent: "space-between",
|
| 238 |
alignItems: "center",
|
|
|
|
|
|
|
| 239 |
borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
|
| 240 |
background: "rgba(4, 7, 17, 0.85)",
|
| 241 |
backdropFilter: "blur(20px)",
|
|
@@ -244,70 +246,71 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 244 |
flexShrink: 0,
|
| 245 |
}}
|
| 246 |
>
|
| 247 |
-
<div style={{ display: "flex", alignItems: "center", gap: "
|
| 248 |
<div
|
| 249 |
style={{
|
| 250 |
-
width: "
|
| 251 |
-
height: "
|
| 252 |
borderRadius: "10px",
|
| 253 |
background: "linear-gradient(135deg, #3b82f6, #8b5cf6)",
|
| 254 |
display: "flex",
|
| 255 |
alignItems: "center",
|
| 256 |
justifyContent: "center",
|
| 257 |
-
fontSize: "
|
| 258 |
fontWeight: 800,
|
| 259 |
boxShadow: "0 0 20px rgba(59, 130, 246, 0.4)",
|
|
|
|
| 260 |
}}
|
| 261 |
>
|
| 262 |
⚡
|
| 263 |
</div>
|
| 264 |
<div>
|
| 265 |
-
<div style={{ fontSize: "
|
| 266 |
PilotMaster
|
| 267 |
</div>
|
| 268 |
-
<div style={{ fontSize: "
|
| 269 |
-
Observable AI
|
| 270 |
</div>
|
| 271 |
</div>
|
| 272 |
</div>
|
| 273 |
|
| 274 |
-
<div style={{ display: "flex", alignItems: "center", gap: "
|
| 275 |
<div
|
| 276 |
style={{
|
| 277 |
display: "flex",
|
| 278 |
alignItems: "center",
|
| 279 |
-
gap: "
|
| 280 |
-
padding: "
|
| 281 |
borderRadius: "9999px",
|
| 282 |
background: "rgba(34, 197, 94, 0.08)",
|
| 283 |
border: "1px solid rgba(34, 197, 94, 0.2)",
|
| 284 |
-
fontSize: "
|
| 285 |
color: "#4ade80",
|
| 286 |
fontWeight: 600,
|
| 287 |
}}
|
| 288 |
>
|
| 289 |
<span
|
| 290 |
style={{
|
| 291 |
-
width: "
|
| 292 |
-
height: "
|
| 293 |
borderRadius: "50%",
|
| 294 |
background: "#22c55e",
|
| 295 |
boxShadow: "0 0 8px #22c55e",
|
| 296 |
}}
|
| 297 |
/>
|
| 298 |
-
Kernel v2.0
|
| 299 |
</div>
|
| 300 |
|
| 301 |
<button
|
| 302 |
onClick={handleQuickDemo}
|
| 303 |
title="Instant one-click access with sample data and live Groq inference"
|
| 304 |
style={{
|
| 305 |
-
padding: "
|
| 306 |
borderRadius: "9999px",
|
| 307 |
background: "linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(168, 85, 247, 0.2))",
|
| 308 |
border: "1px solid rgba(147, 197, 253, 0.3)",
|
| 309 |
color: "#ffffff",
|
| 310 |
-
fontSize: "
|
| 311 |
fontWeight: 600,
|
| 312 |
cursor: "pointer",
|
| 313 |
transition: "all 0.2s ease",
|
|
@@ -321,29 +324,38 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 321 |
e.currentTarget.style.transform = "translateY(0)";
|
| 322 |
}}
|
| 323 |
>
|
| 324 |
-
⚡ Quick Demo
|
| 325 |
</button>
|
| 326 |
</div>
|
| 327 |
</header>
|
| 328 |
|
| 329 |
-
{/* MAIN HERO SPLIT VIEW (
|
| 330 |
<main
|
| 331 |
style={{
|
| 332 |
flex: 1,
|
| 333 |
-
display: "
|
| 334 |
-
|
| 335 |
-
gap: "
|
| 336 |
-
padding: "28px 40px",
|
| 337 |
maxWidth: "1400px",
|
| 338 |
margin: "0 auto",
|
| 339 |
width: "100%",
|
| 340 |
boxSizing: "border-box",
|
| 341 |
zIndex: 1,
|
| 342 |
-
|
| 343 |
}}
|
| 344 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
{/* LEFT COLUMN: ARCHITECTURE TOPOLOGY & DEEP DIVE DESCRIPTION */}
|
| 346 |
-
<div style={{ display: "flex", flexDirection: "column", gap: "
|
| 347 |
{/* Main Title & Ecosystem Subtitle */}
|
| 348 |
<div>
|
| 349 |
<div
|
|
@@ -355,10 +367,11 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 355 |
borderRadius: "9999px",
|
| 356 |
background: "rgba(59, 130, 246, 0.1)",
|
| 357 |
border: "1px solid rgba(59, 130, 246, 0.25)",
|
| 358 |
-
fontSize: "
|
| 359 |
color: "#93c5fd",
|
| 360 |
fontWeight: 600,
|
| 361 |
-
marginBottom: "
|
|
|
|
| 362 |
}}
|
| 363 |
>
|
| 364 |
<span>🔬 Next-Gen RAG Orchestration & Observability</span>
|
|
@@ -366,10 +379,10 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 366 |
<h1
|
| 367 |
style={{
|
| 368 |
margin: "0 0 8px",
|
| 369 |
-
fontSize: "36px",
|
| 370 |
fontWeight: 900,
|
| 371 |
lineHeight: 1.15,
|
| 372 |
-
letterSpacing: "-
|
| 373 |
background: "linear-gradient(135deg, #ffffff 40%, #93c5fd 80%, #c084fc 100%)",
|
| 374 |
WebkitBackgroundClip: "text",
|
| 375 |
WebkitTextFillColor: "transparent",
|
|
@@ -386,8 +399,10 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 386 |
background: "rgba(11, 16, 28, 0.7)",
|
| 387 |
border: "1px solid rgba(255, 255, 255, 0.1)",
|
| 388 |
backdropFilter: "blur(20px)",
|
| 389 |
-
padding: "20px",
|
| 390 |
boxShadow: "0 16px 40px rgba(0, 0, 0, 0.4)",
|
|
|
|
|
|
|
| 391 |
}}
|
| 392 |
>
|
| 393 |
{/* TOPOLOGY TAB SELECTOR */}
|
|
@@ -396,24 +411,26 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 396 |
display: "flex",
|
| 397 |
justifyContent: "space-between",
|
| 398 |
alignItems: "center",
|
| 399 |
-
|
|
|
|
|
|
|
| 400 |
borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
|
| 401 |
paddingBottom: "10px",
|
| 402 |
}}
|
| 403 |
>
|
| 404 |
-
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
| 405 |
-
<span style={{ width: "8px", height: "8px", borderRadius: "50%", background: currentDesc.badgeColor }} />
|
| 406 |
-
<span style={{ fontSize: "11px", fontWeight: 700, color: "#e2e8f0", textTransform: "uppercase", letterSpacing: "0.06em" }}>
|
| 407 |
{currentDesc.title}
|
| 408 |
</span>
|
| 409 |
</div>
|
| 410 |
-
<div style={{ display: "flex", gap: "4px" }}>
|
| 411 |
{["ecosystem", "docpilot", "tracepilot", "gaugepilot"].map((tab) => (
|
| 412 |
<button
|
| 413 |
key={tab}
|
| 414 |
onClick={() => setActiveTab(tab)}
|
| 415 |
style={{
|
| 416 |
-
padding: "4px
|
| 417 |
borderRadius: "6px",
|
| 418 |
background: activeTab === tab ? "rgba(255, 255, 255, 0.12)" : "transparent",
|
| 419 |
border: activeTab === tab ? "1px solid rgba(255, 255, 255, 0.15)" : "1px solid transparent",
|
|
@@ -423,6 +440,7 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 423 |
cursor: "pointer",
|
| 424 |
textTransform: "capitalize",
|
| 425 |
transition: "all 0.15s ease",
|
|
|
|
| 426 |
}}
|
| 427 |
>
|
| 428 |
{tab}
|
|
@@ -431,14 +449,14 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 431 |
</div>
|
| 432 |
</div>
|
| 433 |
|
| 434 |
-
{/* TOPOLOGY NODES GRID */}
|
| 435 |
<div
|
| 436 |
style={{
|
| 437 |
display: "grid",
|
| 438 |
-
gridTemplateColumns: "
|
| 439 |
-
gap: "
|
| 440 |
-
alignItems: "
|
| 441 |
-
marginBottom: "
|
| 442 |
}}
|
| 443 |
>
|
| 444 |
{/* PILLAR 1: DOCPILOT */}
|
|
@@ -447,7 +465,7 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 447 |
onMouseEnter={() => setHoveredNode("docpilot")}
|
| 448 |
onMouseLeave={() => setHoveredNode(null)}
|
| 449 |
style={{
|
| 450 |
-
padding: "
|
| 451 |
borderRadius: "14px",
|
| 452 |
background:
|
| 453 |
activeTab === "docpilot" || hoveredNode === "docpilot"
|
|
@@ -463,8 +481,8 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 463 |
}}
|
| 464 |
>
|
| 465 |
<div style={{ display: "flex", alignItems: "center", gap: "6px", marginBottom: "4px" }}>
|
| 466 |
-
<span style={{ fontSize: "
|
| 467 |
-
<h3 style={{ margin: 0, fontSize: "
|
| 468 |
</div>
|
| 469 |
<p style={{ margin: 0, fontSize: "11px", color: "#cbd5e1", lineHeight: 1.4 }}>
|
| 470 |
Document intelligence & grounded natural language synthesis.
|
|
@@ -477,8 +495,8 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 477 |
onMouseEnter={() => setHoveredNode("pilotcore")}
|
| 478 |
onMouseLeave={() => setHoveredNode(null)}
|
| 479 |
style={{
|
| 480 |
-
padding: "
|
| 481 |
-
borderRadius: "
|
| 482 |
background:
|
| 483 |
activeTab === "ecosystem" || hoveredNode === "pilotcore"
|
| 484 |
? "linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.95))"
|
|
@@ -489,7 +507,8 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 489 |
display: "flex",
|
| 490 |
flexDirection: "column",
|
| 491 |
alignItems: "center",
|
| 492 |
-
|
|
|
|
| 493 |
cursor: "pointer",
|
| 494 |
position: "relative",
|
| 495 |
}}
|
|
@@ -510,9 +529,9 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 510 |
>
|
| 511 |
Core Kernel
|
| 512 |
</div>
|
| 513 |
-
<div style={{ fontSize: "
|
| 514 |
<div>
|
| 515 |
-
<h3 style={{ margin: 0, fontSize: "
|
| 516 |
<div style={{ fontSize: "10px", color: "#94a3b8" }}>
|
| 517 |
Hybrid Vector + Lexical RRF
|
| 518 |
</div>
|
|
@@ -585,21 +604,21 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 585 |
{/* AUTHORITATIVE ECOSYSTEM TEXT & HIGHLIGHTS */}
|
| 586 |
<div
|
| 587 |
style={{
|
| 588 |
-
padding: "
|
| 589 |
borderRadius: "14px",
|
| 590 |
background: "rgba(255, 255, 255, 0.02)",
|
| 591 |
border: "1px solid rgba(255, 255, 255, 0.06)",
|
| 592 |
}}
|
| 593 |
>
|
| 594 |
-
<p style={{ margin: "0 0 10px", fontSize: "
|
| 595 |
{currentDesc.description}
|
| 596 |
</p>
|
| 597 |
-
<div style={{ display: "flex", gap: "
|
| 598 |
{currentDesc.highlights.map((h, idx) => (
|
| 599 |
<span
|
| 600 |
key={idx}
|
| 601 |
style={{
|
| 602 |
-
fontSize: "
|
| 603 |
padding: "3px 8px",
|
| 604 |
borderRadius: "6px",
|
| 605 |
background: "rgba(255, 255, 255, 0.05)",
|
|
@@ -616,16 +635,16 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 616 |
</div>
|
| 617 |
|
| 618 |
{/* RIGHT COLUMN: MAANG-GRADE BALANCED AUTHENTICATION CARD */}
|
| 619 |
-
<div style={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
|
| 620 |
<div
|
| 621 |
style={{
|
| 622 |
width: "100%",
|
| 623 |
-
maxWidth: "
|
| 624 |
borderRadius: "24px",
|
| 625 |
background: "rgba(13, 19, 33, 0.85)",
|
| 626 |
border: "1px solid rgba(255, 255, 255, 0.12)",
|
| 627 |
backdropFilter: "blur(30px)",
|
| 628 |
-
padding: "30px 28px",
|
| 629 |
boxShadow: "0 20px 50px rgba(0, 0, 0, 0.6), 0 0 30px rgba(59, 130, 246, 0.08)",
|
| 630 |
display: "flex",
|
| 631 |
flexDirection: "column",
|
|
@@ -954,17 +973,20 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 954 |
)}
|
| 955 |
</div>
|
| 956 |
</div>
|
| 957 |
-
</
|
|
|
|
| 958 |
|
| 959 |
{/* FOOTER BAR */}
|
| 960 |
<footer
|
| 961 |
style={{
|
| 962 |
-
padding: "16px 36px",
|
| 963 |
borderTop: "1px solid rgba(255, 255, 255, 0.05)",
|
| 964 |
display: "flex",
|
| 965 |
justifyContent: "space-between",
|
| 966 |
alignItems: "center",
|
| 967 |
-
|
|
|
|
|
|
|
| 968 |
color: "#64748b",
|
| 969 |
boxSizing: "border-box",
|
| 970 |
zIndex: 10,
|
|
@@ -972,7 +994,7 @@ export default function OpeningLanding({ onLogin, initialMode = "login" }) {
|
|
| 972 |
}}
|
| 973 |
>
|
| 974 |
<div>PilotMaster Intelligence Platform © 2026. Powered by PilotCore Kernel.</div>
|
| 975 |
-
<div style={{ display: "flex", gap: "
|
| 976 |
<span>Dual Mode (Production & Lab)</span>
|
| 977 |
<span>Active Groq Models</span>
|
| 978 |
<span>PostgreSQL Observability</span>
|
|
|
|
| 232 |
<header
|
| 233 |
style={{
|
| 234 |
width: "100%",
|
| 235 |
+
padding: "14px clamp(16px, 4vw, 36px)",
|
| 236 |
display: "flex",
|
| 237 |
justifyContent: "space-between",
|
| 238 |
alignItems: "center",
|
| 239 |
+
flexWrap: "wrap",
|
| 240 |
+
gap: "12px",
|
| 241 |
borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
|
| 242 |
background: "rgba(4, 7, 17, 0.85)",
|
| 243 |
backdropFilter: "blur(20px)",
|
|
|
|
| 246 |
flexShrink: 0,
|
| 247 |
}}
|
| 248 |
>
|
| 249 |
+
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
| 250 |
<div
|
| 251 |
style={{
|
| 252 |
+
width: "32px",
|
| 253 |
+
height: "32px",
|
| 254 |
borderRadius: "10px",
|
| 255 |
background: "linear-gradient(135deg, #3b82f6, #8b5cf6)",
|
| 256 |
display: "flex",
|
| 257 |
alignItems: "center",
|
| 258 |
justifyContent: "center",
|
| 259 |
+
fontSize: "16px",
|
| 260 |
fontWeight: 800,
|
| 261 |
boxShadow: "0 0 20px rgba(59, 130, 246, 0.4)",
|
| 262 |
+
flexShrink: 0,
|
| 263 |
}}
|
| 264 |
>
|
| 265 |
⚡
|
| 266 |
</div>
|
| 267 |
<div>
|
| 268 |
+
<div style={{ fontSize: "18px", fontWeight: 800, letterSpacing: "-0.5px", color: "#ffffff" }}>
|
| 269 |
PilotMaster
|
| 270 |
</div>
|
| 271 |
+
<div style={{ fontSize: "10px", color: "#94a3b8", letterSpacing: "0.04em", textTransform: "uppercase", fontWeight: 600 }}>
|
| 272 |
+
Observable AI Ecosystem
|
| 273 |
</div>
|
| 274 |
</div>
|
| 275 |
</div>
|
| 276 |
|
| 277 |
+
<div style={{ display: "flex", alignItems: "center", gap: "10px", flexWrap: "wrap" }}>
|
| 278 |
<div
|
| 279 |
style={{
|
| 280 |
display: "flex",
|
| 281 |
alignItems: "center",
|
| 282 |
+
gap: "6px",
|
| 283 |
+
padding: "4px 10px",
|
| 284 |
borderRadius: "9999px",
|
| 285 |
background: "rgba(34, 197, 94, 0.08)",
|
| 286 |
border: "1px solid rgba(34, 197, 94, 0.2)",
|
| 287 |
+
fontSize: "11px",
|
| 288 |
color: "#4ade80",
|
| 289 |
fontWeight: 600,
|
| 290 |
}}
|
| 291 |
>
|
| 292 |
<span
|
| 293 |
style={{
|
| 294 |
+
width: "6px",
|
| 295 |
+
height: "6px",
|
| 296 |
borderRadius: "50%",
|
| 297 |
background: "#22c55e",
|
| 298 |
boxShadow: "0 0 8px #22c55e",
|
| 299 |
}}
|
| 300 |
/>
|
| 301 |
+
Kernel v2.0
|
| 302 |
</div>
|
| 303 |
|
| 304 |
<button
|
| 305 |
onClick={handleQuickDemo}
|
| 306 |
title="Instant one-click access with sample data and live Groq inference"
|
| 307 |
style={{
|
| 308 |
+
padding: "6px 14px",
|
| 309 |
borderRadius: "9999px",
|
| 310 |
background: "linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(168, 85, 247, 0.2))",
|
| 311 |
border: "1px solid rgba(147, 197, 253, 0.3)",
|
| 312 |
color: "#ffffff",
|
| 313 |
+
fontSize: "11px",
|
| 314 |
fontWeight: 600,
|
| 315 |
cursor: "pointer",
|
| 316 |
transition: "all 0.2s ease",
|
|
|
|
| 324 |
e.currentTarget.style.transform = "translateY(0)";
|
| 325 |
}}
|
| 326 |
>
|
| 327 |
+
⚡ Quick Demo
|
| 328 |
</button>
|
| 329 |
</div>
|
| 330 |
</header>
|
| 331 |
|
| 332 |
+
{/* MAIN HERO SPLIT VIEW (RESPONSIVE STACK ON MOBILE) */}
|
| 333 |
<main
|
| 334 |
style={{
|
| 335 |
flex: 1,
|
| 336 |
+
display: "flex",
|
| 337 |
+
flexDirection: "column",
|
| 338 |
+
gap: "28px",
|
| 339 |
+
padding: "clamp(16px, 3vw, 28px) clamp(16px, 4vw, 40px)",
|
| 340 |
maxWidth: "1400px",
|
| 341 |
margin: "0 auto",
|
| 342 |
width: "100%",
|
| 343 |
boxSizing: "border-box",
|
| 344 |
zIndex: 1,
|
| 345 |
+
justifyContent: "center",
|
| 346 |
}}
|
| 347 |
>
|
| 348 |
+
<div
|
| 349 |
+
style={{
|
| 350 |
+
display: "grid",
|
| 351 |
+
gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 460px), 1fr))",
|
| 352 |
+
gap: "28px",
|
| 353 |
+
alignItems: "center",
|
| 354 |
+
width: "100%",
|
| 355 |
+
}}
|
| 356 |
+
>
|
| 357 |
{/* LEFT COLUMN: ARCHITECTURE TOPOLOGY & DEEP DIVE DESCRIPTION */}
|
| 358 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "18px", width: "100%", minWidth: 0 }}>
|
| 359 |
{/* Main Title & Ecosystem Subtitle */}
|
| 360 |
<div>
|
| 361 |
<div
|
|
|
|
| 367 |
borderRadius: "9999px",
|
| 368 |
background: "rgba(59, 130, 246, 0.1)",
|
| 369 |
border: "1px solid rgba(59, 130, 246, 0.25)",
|
| 370 |
+
fontSize: "11px",
|
| 371 |
color: "#93c5fd",
|
| 372 |
fontWeight: 600,
|
| 373 |
+
marginBottom: "8px",
|
| 374 |
+
maxWidth: "100%",
|
| 375 |
}}
|
| 376 |
>
|
| 377 |
<span>🔬 Next-Gen RAG Orchestration & Observability</span>
|
|
|
|
| 379 |
<h1
|
| 380 |
style={{
|
| 381 |
margin: "0 0 8px",
|
| 382 |
+
fontSize: "clamp(24px, 4vw, 36px)",
|
| 383 |
fontWeight: 900,
|
| 384 |
lineHeight: 1.15,
|
| 385 |
+
letterSpacing: "-0.8px",
|
| 386 |
background: "linear-gradient(135deg, #ffffff 40%, #93c5fd 80%, #c084fc 100%)",
|
| 387 |
WebkitBackgroundClip: "text",
|
| 388 |
WebkitTextFillColor: "transparent",
|
|
|
|
| 399 |
background: "rgba(11, 16, 28, 0.7)",
|
| 400 |
border: "1px solid rgba(255, 255, 255, 0.1)",
|
| 401 |
backdropFilter: "blur(20px)",
|
| 402 |
+
padding: "clamp(14px, 2.5vw, 20px)",
|
| 403 |
boxShadow: "0 16px 40px rgba(0, 0, 0, 0.4)",
|
| 404 |
+
width: "100%",
|
| 405 |
+
boxSizing: "border-box",
|
| 406 |
}}
|
| 407 |
>
|
| 408 |
{/* TOPOLOGY TAB SELECTOR */}
|
|
|
|
| 411 |
display: "flex",
|
| 412 |
justifyContent: "space-between",
|
| 413 |
alignItems: "center",
|
| 414 |
+
flexWrap: "wrap",
|
| 415 |
+
gap: "8px",
|
| 416 |
+
marginBottom: "14px",
|
| 417 |
borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
|
| 418 |
paddingBottom: "10px",
|
| 419 |
}}
|
| 420 |
>
|
| 421 |
+
<div style={{ display: "flex", alignItems: "center", gap: "8px", minWidth: 0 }}>
|
| 422 |
+
<span style={{ width: "8px", height: "8px", borderRadius: "50%", background: currentDesc.badgeColor, flexShrink: 0 }} />
|
| 423 |
+
<span style={{ fontSize: "11px", fontWeight: 700, color: "#e2e8f0", textTransform: "uppercase", letterSpacing: "0.06em", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
| 424 |
{currentDesc.title}
|
| 425 |
</span>
|
| 426 |
</div>
|
| 427 |
+
<div style={{ display: "flex", gap: "4px", overflowX: "auto", maxWidth: "100%", paddingBottom: "2px" }}>
|
| 428 |
{["ecosystem", "docpilot", "tracepilot", "gaugepilot"].map((tab) => (
|
| 429 |
<button
|
| 430 |
key={tab}
|
| 431 |
onClick={() => setActiveTab(tab)}
|
| 432 |
style={{
|
| 433 |
+
padding: "4px 9px",
|
| 434 |
borderRadius: "6px",
|
| 435 |
background: activeTab === tab ? "rgba(255, 255, 255, 0.12)" : "transparent",
|
| 436 |
border: activeTab === tab ? "1px solid rgba(255, 255, 255, 0.15)" : "1px solid transparent",
|
|
|
|
| 440 |
cursor: "pointer",
|
| 441 |
textTransform: "capitalize",
|
| 442 |
transition: "all 0.15s ease",
|
| 443 |
+
whiteSpace: "nowrap",
|
| 444 |
}}
|
| 445 |
>
|
| 446 |
{tab}
|
|
|
|
| 449 |
</div>
|
| 450 |
</div>
|
| 451 |
|
| 452 |
+
{/* TOPOLOGY NODES GRID (RESPONSIVE WRAP) */}
|
| 453 |
<div
|
| 454 |
style={{
|
| 455 |
display: "grid",
|
| 456 |
+
gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 140px), 1fr))",
|
| 457 |
+
gap: "10px",
|
| 458 |
+
alignItems: "stretch",
|
| 459 |
+
marginBottom: "14px",
|
| 460 |
}}
|
| 461 |
>
|
| 462 |
{/* PILLAR 1: DOCPILOT */}
|
|
|
|
| 465 |
onMouseEnter={() => setHoveredNode("docpilot")}
|
| 466 |
onMouseLeave={() => setHoveredNode(null)}
|
| 467 |
style={{
|
| 468 |
+
padding: "12px",
|
| 469 |
borderRadius: "14px",
|
| 470 |
background:
|
| 471 |
activeTab === "docpilot" || hoveredNode === "docpilot"
|
|
|
|
| 481 |
}}
|
| 482 |
>
|
| 483 |
<div style={{ display: "flex", alignItems: "center", gap: "6px", marginBottom: "4px" }}>
|
| 484 |
+
<span style={{ fontSize: "15px" }}>📄</span>
|
| 485 |
+
<h3 style={{ margin: 0, fontSize: "13px", fontWeight: 800, color: "#60a5fa" }}>DocPilot</h3>
|
| 486 |
</div>
|
| 487 |
<p style={{ margin: 0, fontSize: "11px", color: "#cbd5e1", lineHeight: 1.4 }}>
|
| 488 |
Document intelligence & grounded natural language synthesis.
|
|
|
|
| 495 |
onMouseEnter={() => setHoveredNode("pilotcore")}
|
| 496 |
onMouseLeave={() => setHoveredNode(null)}
|
| 497 |
style={{
|
| 498 |
+
padding: "14px 10px",
|
| 499 |
+
borderRadius: "16px",
|
| 500 |
background:
|
| 501 |
activeTab === "ecosystem" || hoveredNode === "pilotcore"
|
| 502 |
? "linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.95))"
|
|
|
|
| 507 |
display: "flex",
|
| 508 |
flexDirection: "column",
|
| 509 |
alignItems: "center",
|
| 510 |
+
justifyContent: "center",
|
| 511 |
+
gap: "4px",
|
| 512 |
cursor: "pointer",
|
| 513 |
position: "relative",
|
| 514 |
}}
|
|
|
|
| 529 |
>
|
| 530 |
Core Kernel
|
| 531 |
</div>
|
| 532 |
+
<div style={{ fontSize: "18px" }}>⚡</div>
|
| 533 |
<div>
|
| 534 |
+
<h3 style={{ margin: 0, fontSize: "14px", fontWeight: 900, color: "#38bdf8" }}>PilotCore</h3>
|
| 535 |
<div style={{ fontSize: "10px", color: "#94a3b8" }}>
|
| 536 |
Hybrid Vector + Lexical RRF
|
| 537 |
</div>
|
|
|
|
| 604 |
{/* AUTHORITATIVE ECOSYSTEM TEXT & HIGHLIGHTS */}
|
| 605 |
<div
|
| 606 |
style={{
|
| 607 |
+
padding: "12px 14px",
|
| 608 |
borderRadius: "14px",
|
| 609 |
background: "rgba(255, 255, 255, 0.02)",
|
| 610 |
border: "1px solid rgba(255, 255, 255, 0.06)",
|
| 611 |
}}
|
| 612 |
>
|
| 613 |
+
<p style={{ margin: "0 0 10px", fontSize: "12px", lineHeight: 1.6, color: "#cbd5e1" }}>
|
| 614 |
{currentDesc.description}
|
| 615 |
</p>
|
| 616 |
+
<div style={{ display: "flex", gap: "6px", flexWrap: "wrap" }}>
|
| 617 |
{currentDesc.highlights.map((h, idx) => (
|
| 618 |
<span
|
| 619 |
key={idx}
|
| 620 |
style={{
|
| 621 |
+
fontSize: "10.5px",
|
| 622 |
padding: "3px 8px",
|
| 623 |
borderRadius: "6px",
|
| 624 |
background: "rgba(255, 255, 255, 0.05)",
|
|
|
|
| 635 |
</div>
|
| 636 |
|
| 637 |
{/* RIGHT COLUMN: MAANG-GRADE BALANCED AUTHENTICATION CARD */}
|
| 638 |
+
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", width: "100%" }}>
|
| 639 |
<div
|
| 640 |
style={{
|
| 641 |
width: "100%",
|
| 642 |
+
maxWidth: "420px",
|
| 643 |
borderRadius: "24px",
|
| 644 |
background: "rgba(13, 19, 33, 0.85)",
|
| 645 |
border: "1px solid rgba(255, 255, 255, 0.12)",
|
| 646 |
backdropFilter: "blur(30px)",
|
| 647 |
+
padding: "clamp(20px, 4vw, 30px) clamp(16px, 3.5vw, 28px)",
|
| 648 |
boxShadow: "0 20px 50px rgba(0, 0, 0, 0.6), 0 0 30px rgba(59, 130, 246, 0.08)",
|
| 649 |
display: "flex",
|
| 650 |
flexDirection: "column",
|
|
|
|
| 973 |
)}
|
| 974 |
</div>
|
| 975 |
</div>
|
| 976 |
+
</div>
|
| 977 |
+
</main>
|
| 978 |
|
| 979 |
{/* FOOTER BAR */}
|
| 980 |
<footer
|
| 981 |
style={{
|
| 982 |
+
padding: "14px clamp(16px, 4vw, 36px)",
|
| 983 |
borderTop: "1px solid rgba(255, 255, 255, 0.05)",
|
| 984 |
display: "flex",
|
| 985 |
justifyContent: "space-between",
|
| 986 |
alignItems: "center",
|
| 987 |
+
flexWrap: "wrap",
|
| 988 |
+
gap: "10px",
|
| 989 |
+
fontSize: "11px",
|
| 990 |
color: "#64748b",
|
| 991 |
boxSizing: "border-box",
|
| 992 |
zIndex: 10,
|
|
|
|
| 994 |
}}
|
| 995 |
>
|
| 996 |
<div>PilotMaster Intelligence Platform © 2026. Powered by PilotCore Kernel.</div>
|
| 997 |
+
<div style={{ display: "flex", gap: "12px", flexWrap: "wrap" }}>
|
| 998 |
<span>Dual Mode (Production & Lab)</span>
|
| 999 |
<span>Active Groq Models</span>
|
| 1000 |
<span>PostgreSQL Observability</span>
|
frontend/src/docpilot/pages/Dashboard.jsx
CHANGED
|
@@ -65,7 +65,8 @@ function CustomSelector({ label, sublabel, open, onToggle, selectorRef, children
|
|
| 65 |
position: "absolute",
|
| 66 |
bottom: "calc(100% + 8px)",
|
| 67 |
left: 0,
|
| 68 |
-
width: "
|
|
|
|
| 69 |
maxHeight: "360px",
|
| 70 |
overflowY: "auto",
|
| 71 |
zIndex: 9999,
|
|
@@ -1921,10 +1922,22 @@ function Dashboard({
|
|
| 1921 |
alignItems: "center",
|
| 1922 |
justifyContent: "space-between",
|
| 1923 |
gap: "8px",
|
| 1924 |
-
|
|
|
|
| 1925 |
}}
|
| 1926 |
>
|
| 1927 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1928 |
{/* MODEL SELECTOR */}
|
| 1929 |
<CustomSelector
|
| 1930 |
label={activeModelLabel}
|
|
@@ -2213,7 +2226,8 @@ function Dashboard({
|
|
| 2213 |
position: "absolute",
|
| 2214 |
bottom: "calc(100% + 8px)",
|
| 2215 |
left: 0,
|
| 2216 |
-
width: "
|
|
|
|
| 2217 |
maxHeight: "440px",
|
| 2218 |
overflowY: "auto",
|
| 2219 |
zIndex: 9999,
|
|
|
|
| 65 |
position: "absolute",
|
| 66 |
bottom: "calc(100% + 8px)",
|
| 67 |
left: 0,
|
| 68 |
+
width: "min(320px, calc(100vw - 28px))",
|
| 69 |
+
maxWidth: "calc(100vw - 28px)",
|
| 70 |
maxHeight: "360px",
|
| 71 |
overflowY: "auto",
|
| 72 |
zIndex: 9999,
|
|
|
|
| 1922 |
alignItems: "center",
|
| 1923 |
justifyContent: "space-between",
|
| 1924 |
gap: "8px",
|
| 1925 |
+
width: "100%",
|
| 1926 |
+
minWidth: 0,
|
| 1927 |
}}
|
| 1928 |
>
|
| 1929 |
+
<div
|
| 1930 |
+
className="pill-scroll-bar"
|
| 1931 |
+
style={{
|
| 1932 |
+
display: "flex",
|
| 1933 |
+
alignItems: "center",
|
| 1934 |
+
gap: "6px",
|
| 1935 |
+
overflowX: "auto",
|
| 1936 |
+
flex: 1,
|
| 1937 |
+
minWidth: 0,
|
| 1938 |
+
paddingBottom: "2px",
|
| 1939 |
+
}}
|
| 1940 |
+
>
|
| 1941 |
{/* MODEL SELECTOR */}
|
| 1942 |
<CustomSelector
|
| 1943 |
label={activeModelLabel}
|
|
|
|
| 2226 |
position: "absolute",
|
| 2227 |
bottom: "calc(100% + 8px)",
|
| 2228 |
left: 0,
|
| 2229 |
+
width: "min(340px, calc(100vw - 32px))",
|
| 2230 |
+
maxWidth: "calc(100vw - 32px)",
|
| 2231 |
maxHeight: "440px",
|
| 2232 |
overflowY: "auto",
|
| 2233 |
zIndex: 9999,
|
frontend/src/gaugepilot/components/ConfigBadge.jsx
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useRef, useEffect } from "react";
|
| 2 |
+
import { parseConfigDetails } from "../utils/configUtils";
|
| 3 |
+
|
| 4 |
+
/**
|
| 5 |
+
* Interactive Configuration Badge with detailed specification hover card / tooltip.
|
| 6 |
+
*/
|
| 7 |
+
export default function ConfigBadge({
|
| 8 |
+
configName,
|
| 9 |
+
label = null,
|
| 10 |
+
isBest = false,
|
| 11 |
+
medal = null,
|
| 12 |
+
fontSize = "13px",
|
| 13 |
+
}) {
|
| 14 |
+
const [hovered, setHovered] = useState(false);
|
| 15 |
+
const [popoverPos, setPopoverPos] = useState({ top: 0, left: 0, placeAbove: true });
|
| 16 |
+
const badgeRef = useRef(null);
|
| 17 |
+
|
| 18 |
+
const displayLabel = label || configName || "Config";
|
| 19 |
+
const details = parseConfigDetails(configName);
|
| 20 |
+
|
| 21 |
+
const handleMouseEnter = () => {
|
| 22 |
+
if (badgeRef.current) {
|
| 23 |
+
const rect = badgeRef.current.getBoundingClientRect();
|
| 24 |
+
const placeAbove = rect.top > 260; // place above if room, else below
|
| 25 |
+
setPopoverPos({
|
| 26 |
+
top: placeAbove ? rect.top - 8 : rect.bottom + 8,
|
| 27 |
+
left: rect.left + rect.width / 2,
|
| 28 |
+
placeAbove,
|
| 29 |
+
});
|
| 30 |
+
}
|
| 31 |
+
setHovered(true);
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
return (
|
| 35 |
+
<div
|
| 36 |
+
ref={badgeRef}
|
| 37 |
+
onMouseEnter={handleMouseEnter}
|
| 38 |
+
onMouseLeave={() => setHovered(false)}
|
| 39 |
+
onFocus={handleMouseEnter}
|
| 40 |
+
onBlur={() => setHovered(false)}
|
| 41 |
+
tabIndex={0}
|
| 42 |
+
style={{
|
| 43 |
+
display: "inline-flex",
|
| 44 |
+
alignItems: "center",
|
| 45 |
+
gap: "8px",
|
| 46 |
+
position: "relative",
|
| 47 |
+
cursor: "pointer",
|
| 48 |
+
outline: "none",
|
| 49 |
+
}}
|
| 50 |
+
>
|
| 51 |
+
{medal && (
|
| 52 |
+
<span
|
| 53 |
+
style={{
|
| 54 |
+
display: "inline-flex",
|
| 55 |
+
alignItems: "center",
|
| 56 |
+
justifyContent: "center",
|
| 57 |
+
width: 24,
|
| 58 |
+
height: 24,
|
| 59 |
+
borderRadius: "6px",
|
| 60 |
+
background: medal.color ? `${medal.color}18` : "rgba(255,255,255,0.04)",
|
| 61 |
+
fontSize: "14px",
|
| 62 |
+
fontWeight: 700,
|
| 63 |
+
color: medal.color || "#ffffff",
|
| 64 |
+
}}
|
| 65 |
+
>
|
| 66 |
+
{medal.label}
|
| 67 |
+
</span>
|
| 68 |
+
)}
|
| 69 |
+
|
| 70 |
+
{isBest && (
|
| 71 |
+
<span
|
| 72 |
+
style={{
|
| 73 |
+
fontSize: "10px",
|
| 74 |
+
fontWeight: 700,
|
| 75 |
+
padding: "2px 6px",
|
| 76 |
+
borderRadius: "4px",
|
| 77 |
+
background: "rgba(245, 158, 11, 0.15)",
|
| 78 |
+
border: "1px solid rgba(245, 158, 11, 0.3)",
|
| 79 |
+
color: "#f59e0b",
|
| 80 |
+
letterSpacing: "0.05em",
|
| 81 |
+
textTransform: "uppercase",
|
| 82 |
+
}}
|
| 83 |
+
>
|
| 84 |
+
Best
|
| 85 |
+
</span>
|
| 86 |
+
)}
|
| 87 |
+
|
| 88 |
+
{/* Main Config Badge */}
|
| 89 |
+
<span
|
| 90 |
+
style={{
|
| 91 |
+
display: "inline-flex",
|
| 92 |
+
alignItems: "center",
|
| 93 |
+
gap: "6px",
|
| 94 |
+
padding: "3px 10px",
|
| 95 |
+
borderRadius: "8px",
|
| 96 |
+
background: hovered ? "rgba(168, 85, 247, 0.2)" : "rgba(255, 255, 255, 0.05)",
|
| 97 |
+
border: hovered ? "1px solid rgba(168, 85, 247, 0.4)" : "1px solid rgba(255, 255, 255, 0.08)",
|
| 98 |
+
color: hovered ? "#ffffff" : "rgba(255, 255, 255, 0.9)",
|
| 99 |
+
fontWeight: isBest ? 700 : 600,
|
| 100 |
+
fontSize,
|
| 101 |
+
letterSpacing: "-0.2px",
|
| 102 |
+
transition: "all 0.15s ease",
|
| 103 |
+
boxShadow: hovered ? "0 4px 16px rgba(168, 85, 247, 0.2)" : "none",
|
| 104 |
+
}}
|
| 105 |
+
>
|
| 106 |
+
<span>{displayLabel}</span>
|
| 107 |
+
<span
|
| 108 |
+
style={{
|
| 109 |
+
fontSize: "10px",
|
| 110 |
+
color: hovered ? "#c084fc" : "rgba(255, 255, 255, 0.4)",
|
| 111 |
+
fontWeight: 400,
|
| 112 |
+
}}
|
| 113 |
+
>
|
| 114 |
+
ℹ
|
| 115 |
+
</span>
|
| 116 |
+
</span>
|
| 117 |
+
|
| 118 |
+
{/* Floating Hover Card / Tooltip Popover */}
|
| 119 |
+
{hovered && (
|
| 120 |
+
<div
|
| 121 |
+
style={{
|
| 122 |
+
position: "fixed",
|
| 123 |
+
top: popoverPos.placeAbove ? popoverPos.top : popoverPos.top,
|
| 124 |
+
left: popoverPos.left,
|
| 125 |
+
transform: popoverPos.placeAbove ? "translate(-50%, -100%)" : "translate(-50%, 0)",
|
| 126 |
+
zIndex: 99999,
|
| 127 |
+
width: "min(320px, calc(100vw - 32px))",
|
| 128 |
+
padding: "14px 16px",
|
| 129 |
+
borderRadius: "16px",
|
| 130 |
+
background: "rgba(15, 20, 36, 0.96)",
|
| 131 |
+
border: "1px solid rgba(168, 85, 247, 0.35)",
|
| 132 |
+
backdropFilter: "blur(20px)",
|
| 133 |
+
boxShadow: "0 20px 40px rgba(0, 0, 0, 0.7), 0 0 20px rgba(168, 85, 247, 0.15)",
|
| 134 |
+
pointerEvents: "none",
|
| 135 |
+
boxSizing: "border-box",
|
| 136 |
+
animation: "fadeIn 0.15s ease-out",
|
| 137 |
+
}}
|
| 138 |
+
>
|
| 139 |
+
{/* Header */}
|
| 140 |
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "10px", borderBottom: "1px solid rgba(255,255,255,0.08)", paddingBottom: "8px" }}>
|
| 141 |
+
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
|
| 142 |
+
<span style={{ fontSize: "14px" }}>⚙️</span>
|
| 143 |
+
<span style={{ fontSize: "13px", fontWeight: 700, color: "#ffffff" }}>
|
| 144 |
+
{displayLabel} Specifications
|
| 145 |
+
</span>
|
| 146 |
+
</div>
|
| 147 |
+
{isBest && (
|
| 148 |
+
<span style={{ fontSize: "9px", fontWeight: 700, padding: "1px 5px", borderRadius: "4px", background: "rgba(245, 158, 11, 0.2)", color: "#fbbf24", textTransform: "uppercase" }}>
|
| 149 |
+
Top Rank
|
| 150 |
+
</span>
|
| 151 |
+
)}
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
{/* Breakdown Rows */}
|
| 155 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "6px", fontSize: "11.5px" }}>
|
| 156 |
+
<div style={{ display: "flex", justifyContent: "space-between", gap: "8px" }}>
|
| 157 |
+
<span style={{ color: "rgba(255,255,255,0.5)" }}>🤖 LLM Model:</span>
|
| 158 |
+
<span style={{ color: "#ffffff", fontWeight: 600, textAlign: "right" }}>{details.model}</span>
|
| 159 |
+
</div>
|
| 160 |
+
|
| 161 |
+
<div style={{ display: "flex", justifyContent: "space-between", gap: "8px" }}>
|
| 162 |
+
<span style={{ color: "rgba(255,255,255,0.5)" }}>⚡ Retrieval:</span>
|
| 163 |
+
<span style={{ color: "#38bdf8", fontWeight: 600, textAlign: "right" }}>{details.retrieval}</span>
|
| 164 |
+
</div>
|
| 165 |
+
|
| 166 |
+
<div style={{ display: "flex", justifyContent: "space-between", gap: "8px" }}>
|
| 167 |
+
<span style={{ color: "rgba(255,255,255,0.5)" }}>🎯 Reranker:</span>
|
| 168 |
+
<span style={{ color: "#c084fc", fontWeight: 600, textAlign: "right" }}>{details.reranker}</span>
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
<div style={{ display: "flex", justifyContent: "space-between", gap: "8px" }}>
|
| 172 |
+
<span style={{ color: "rgba(255,255,255,0.5)" }}>📄 Chunker:</span>
|
| 173 |
+
<span style={{ color: "rgba(255,255,255,0.85)", textAlign: "right" }}>{details.chunker}</span>
|
| 174 |
+
</div>
|
| 175 |
+
|
| 176 |
+
<div style={{ display: "flex", justifyContent: "space-between", gap: "8px" }}>
|
| 177 |
+
<span style={{ color: "rgba(255,255,255,0.5)" }}>🧬 Embedding:</span>
|
| 178 |
+
<span style={{ color: "rgba(255,255,255,0.85)", textAlign: "right" }}>{details.embedding}</span>
|
| 179 |
+
</div>
|
| 180 |
+
|
| 181 |
+
<div style={{ marginTop: "4px", paddingTop: "6px", borderTop: "1px solid rgba(255,255,255,0.06)" }}>
|
| 182 |
+
<div style={{ color: "rgba(255,255,255,0.5)", marginBottom: "4px" }}>🧩 Active Enhancements:</div>
|
| 183 |
+
<div style={{ display: "flex", flexWrap: "wrap", gap: "4px" }}>
|
| 184 |
+
{details.enhancements.map((enh, idx) => (
|
| 185 |
+
<span
|
| 186 |
+
key={idx}
|
| 187 |
+
style={{
|
| 188 |
+
fontSize: "10px",
|
| 189 |
+
padding: "2px 6px",
|
| 190 |
+
borderRadius: "6px",
|
| 191 |
+
background: enh.includes("Default") ? "rgba(255,255,255,0.05)" : "rgba(168, 85, 247, 0.2)",
|
| 192 |
+
color: enh.includes("Default") ? "rgba(255,255,255,0.6)" : "#d8b4fe",
|
| 193 |
+
fontWeight: 500,
|
| 194 |
+
}}
|
| 195 |
+
>
|
| 196 |
+
{enh}
|
| 197 |
+
</span>
|
| 198 |
+
))}
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
|
| 202 |
+
{/* Raw signature */}
|
| 203 |
+
<div style={{ marginTop: "6px", paddingTop: "6px", borderTop: "1px solid rgba(255,255,255,0.06)", fontSize: "10px", color: "rgba(255,255,255,0.35)", wordBreak: "break-all", fontFamily: "monospace" }}>
|
| 204 |
+
Signature: {details.rawName}
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
)}
|
| 209 |
+
</div>
|
| 210 |
+
);
|
| 211 |
+
}
|
frontend/src/gaugepilot/components/CustomSelector.jsx
CHANGED
|
@@ -64,7 +64,8 @@ export function CustomSelector({
|
|
| 64 |
position: "absolute",
|
| 65 |
top: "calc(100% + 8px)",
|
| 66 |
left: 0,
|
| 67 |
-
width: "
|
|
|
|
| 68 |
maxHeight: "360px",
|
| 69 |
overflowY: "auto",
|
| 70 |
zIndex: 9999,
|
|
|
|
| 64 |
position: "absolute",
|
| 65 |
top: "calc(100% + 8px)",
|
| 66 |
left: 0,
|
| 67 |
+
width: "min(320px, calc(100vw - 28px))",
|
| 68 |
+
maxWidth: "calc(100vw - 28px)",
|
| 69 |
maxHeight: "360px",
|
| 70 |
overflowY: "auto",
|
| 71 |
zIndex: 9999,
|
frontend/src/gaugepilot/components/leaderboards/OverallTable.jsx
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
if (!data || data.length === 0) {
|
| 3 |
return (
|
| 4 |
<div style={{
|
|
@@ -18,9 +22,10 @@ export default function OverallTable({ data }) {
|
|
| 18 |
|
| 19 |
const isOverall = data[0]?.metrics !== undefined;
|
| 20 |
|
| 21 |
-
const
|
| 22 |
-
|
| 23 |
-
|
|
|
|
| 24 |
|
| 25 |
const getMedal = (rank) => {
|
| 26 |
if (rank === 1) return { label: "🥇", color: "#f59e0b" };
|
|
@@ -69,10 +74,11 @@ export default function OverallTable({ data }) {
|
|
| 69 |
</span>
|
| 70 |
);
|
| 71 |
|
|
|
|
| 72 |
if (!isOverall) {
|
| 73 |
return (
|
| 74 |
-
<div style={{ overflowX: "auto" }}>
|
| 75 |
-
<table style={{ width: "100%", borderCollapse: "collapse", maxWidth: "600px" }}>
|
| 76 |
<thead>
|
| 77 |
<tr>
|
| 78 |
<th style={{ ...th, width: "70px", textAlign: "center" }}>Rank</th>
|
|
@@ -83,6 +89,7 @@ export default function OverallTable({ data }) {
|
|
| 83 |
<tbody>
|
| 84 |
{data.map((row, i) => {
|
| 85 |
const medal = getMedal(i + 1);
|
|
|
|
| 86 |
return (
|
| 87 |
<tr
|
| 88 |
key={row.config_name}
|
|
@@ -100,7 +107,11 @@ export default function OverallTable({ data }) {
|
|
| 100 |
}}>{medal.label}</span>
|
| 101 |
</td>
|
| 102 |
<td style={td}>
|
| 103 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
</td>
|
| 105 |
<td style={{ ...td, textAlign: "right" }}>
|
| 106 |
{metricCell(row.value)}
|
|
@@ -114,9 +125,10 @@ export default function OverallTable({ data }) {
|
|
| 114 |
);
|
| 115 |
}
|
| 116 |
|
|
|
|
| 117 |
return (
|
| 118 |
-
<div style={{ overflowX: "auto" }}>
|
| 119 |
-
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
| 120 |
<thead>
|
| 121 |
<tr>
|
| 122 |
<th style={{ ...th, textAlign: "center", width: "60px" }}>Rank</th>
|
|
@@ -132,6 +144,7 @@ export default function OverallTable({ data }) {
|
|
| 132 |
<tbody>
|
| 133 |
{data.map((row, i) => {
|
| 134 |
const medal = getMedal(i + 1);
|
|
|
|
| 135 |
return (
|
| 136 |
<tr
|
| 137 |
key={row.config_name}
|
|
@@ -149,19 +162,11 @@ export default function OverallTable({ data }) {
|
|
| 149 |
}}>{medal.label}</span>
|
| 150 |
</td>
|
| 151 |
<td style={td}>
|
| 152 |
-
<
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
border: "1px solid rgba(245,158,11,0.3)", color: "#f59e0b",
|
| 158 |
-
letterSpacing: "0.05em", textTransform: "uppercase",
|
| 159 |
-
}}>Best</span>
|
| 160 |
-
)}
|
| 161 |
-
<span style={{ fontWeight: i === 0 ? 600 : 400, color: i === 0 ? "white" : "rgba(255,255,255,0.75)" }}>
|
| 162 |
-
{formatConfigName(row.config_name)}
|
| 163 |
-
</span>
|
| 164 |
-
</div>
|
| 165 |
</td>
|
| 166 |
<td style={{ ...td, textAlign: "right" }}>
|
| 167 |
<span style={{ fontFamily: "'Courier New', monospace", fontWeight: 600, color: "rgba(255,255,255,0.7)" }}>
|
|
|
|
| 1 |
+
import { useMemo } from "react";
|
| 2 |
+
import ConfigBadge from "../ConfigBadge";
|
| 3 |
+
import { buildConfigLabelMap } from "../../utils/configUtils";
|
| 4 |
+
|
| 5 |
+
export default function OverallTable({ data, labelMap = null }) {
|
| 6 |
if (!data || data.length === 0) {
|
| 7 |
return (
|
| 8 |
<div style={{
|
|
|
|
| 22 |
|
| 23 |
const isOverall = data[0]?.metrics !== undefined;
|
| 24 |
|
| 25 |
+
const resolvedLabelMap = useMemo(() => {
|
| 26 |
+
if (labelMap && labelMap.size > 0) return labelMap;
|
| 27 |
+
return buildConfigLabelMap(data);
|
| 28 |
+
}, [labelMap, data]);
|
| 29 |
|
| 30 |
const getMedal = (rank) => {
|
| 31 |
if (rank === 1) return { label: "🥇", color: "#f59e0b" };
|
|
|
|
| 74 |
</span>
|
| 75 |
);
|
| 76 |
|
| 77 |
+
// Single Metric View
|
| 78 |
if (!isOverall) {
|
| 79 |
return (
|
| 80 |
+
<div className="table-scroll-container" style={{ overflowX: "auto", WebkitOverflowScrolling: "touch" }}>
|
| 81 |
+
<table style={{ width: "100%", borderCollapse: "collapse", minWidth: "460px", maxWidth: "600px" }}>
|
| 82 |
<thead>
|
| 83 |
<tr>
|
| 84 |
<th style={{ ...th, width: "70px", textAlign: "center" }}>Rank</th>
|
|
|
|
| 89 |
<tbody>
|
| 90 |
{data.map((row, i) => {
|
| 91 |
const medal = getMedal(i + 1);
|
| 92 |
+
const configLabel = resolvedLabelMap.get(row.config_name) || `Config ${i + 1}`;
|
| 93 |
return (
|
| 94 |
<tr
|
| 95 |
key={row.config_name}
|
|
|
|
| 107 |
}}>{medal.label}</span>
|
| 108 |
</td>
|
| 109 |
<td style={td}>
|
| 110 |
+
<ConfigBadge
|
| 111 |
+
configName={row.config_name}
|
| 112 |
+
label={configLabel}
|
| 113 |
+
isBest={i === 0}
|
| 114 |
+
/>
|
| 115 |
</td>
|
| 116 |
<td style={{ ...td, textAlign: "right" }}>
|
| 117 |
{metricCell(row.value)}
|
|
|
|
| 125 |
);
|
| 126 |
}
|
| 127 |
|
| 128 |
+
// Overall Combined View
|
| 129 |
return (
|
| 130 |
+
<div className="table-scroll-container" style={{ overflowX: "auto", WebkitOverflowScrolling: "touch" }}>
|
| 131 |
+
<table style={{ width: "100%", borderCollapse: "collapse", minWidth: "680px" }}>
|
| 132 |
<thead>
|
| 133 |
<tr>
|
| 134 |
<th style={{ ...th, textAlign: "center", width: "60px" }}>Rank</th>
|
|
|
|
| 144 |
<tbody>
|
| 145 |
{data.map((row, i) => {
|
| 146 |
const medal = getMedal(i + 1);
|
| 147 |
+
const configLabel = resolvedLabelMap.get(row.config_name) || `Config ${i + 1}`;
|
| 148 |
return (
|
| 149 |
<tr
|
| 150 |
key={row.config_name}
|
|
|
|
| 162 |
}}>{medal.label}</span>
|
| 163 |
</td>
|
| 164 |
<td style={td}>
|
| 165 |
+
<ConfigBadge
|
| 166 |
+
configName={row.config_name}
|
| 167 |
+
label={configLabel}
|
| 168 |
+
isBest={i === 0}
|
| 169 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
</td>
|
| 171 |
<td style={{ ...td, textAlign: "right" }}>
|
| 172 |
<span style={{ fontFamily: "'Courier New', monospace", fontWeight: 600, color: "rgba(255,255,255,0.7)" }}>
|
frontend/src/gaugepilot/components/visualizations/HeatmapView.jsx
CHANGED
|
@@ -1,36 +1,18 @@
|
|
| 1 |
import Plot from "react-plotly.js";
|
|
|
|
| 2 |
|
| 3 |
const METRICS = [
|
| 4 |
-
{
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
},
|
| 8 |
-
{
|
| 9 |
-
key: "grounding",
|
| 10 |
-
label: "Grounding",
|
| 11 |
-
},
|
| 12 |
-
{
|
| 13 |
-
key: "quality",
|
| 14 |
-
label: "Quality",
|
| 15 |
-
},
|
| 16 |
-
{
|
| 17 |
-
key: "coverage",
|
| 18 |
-
label: "Coverage",
|
| 19 |
-
},
|
| 20 |
-
{
|
| 21 |
-
key: "latency",
|
| 22 |
-
label: "Latency",
|
| 23 |
-
},
|
| 24 |
];
|
| 25 |
|
| 26 |
-
const LOWER_IS_BETTER = new Set([
|
| 27 |
-
"latency",
|
| 28 |
-
]);
|
| 29 |
|
| 30 |
function normalizeColumn(values, invert = false) {
|
| 31 |
-
const valid = values.filter(
|
| 32 |
-
(v) => v != null && !Number.isNaN(v)
|
| 33 |
-
);
|
| 34 |
|
| 35 |
if (!valid.length) {
|
| 36 |
return values.map(() => null);
|
|
@@ -42,86 +24,45 @@ function normalizeColumn(values, invert = false) {
|
|
| 42 |
|
| 43 |
return values.map((v) => {
|
| 44 |
if (v == null) return null;
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
range === 0
|
| 48 |
-
? 1
|
| 49 |
-
: (v - min) / range;
|
| 50 |
-
|
| 51 |
-
if (invert) {
|
| 52 |
-
score = 1 - score;
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
return score;
|
| 56 |
});
|
| 57 |
}
|
| 58 |
|
| 59 |
-
export default function HeatmapView({
|
| 60 |
-
data,
|
| 61 |
-
}) {
|
| 62 |
const rows = data ?? [];
|
| 63 |
|
| 64 |
if (!rows.length) {
|
| 65 |
return (
|
| 66 |
-
<p
|
| 67 |
-
|
| 68 |
-
color:
|
| 69 |
-
"rgba(255,255,255,0.4)",
|
| 70 |
-
margin: 0,
|
| 71 |
-
}}
|
| 72 |
-
>
|
| 73 |
-
No configurations to compare
|
| 74 |
-
yet.
|
| 75 |
</p>
|
| 76 |
);
|
| 77 |
}
|
| 78 |
|
| 79 |
const z = rows.map((row, i) => {
|
| 80 |
-
return METRICS.map(
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
const normalized =
|
| 87 |
-
normalizeColumn(
|
| 88 |
-
values,
|
| 89 |
-
LOWER_IS_BETTER.has(key)
|
| 90 |
-
);
|
| 91 |
-
|
| 92 |
-
return normalized[i];
|
| 93 |
-
}
|
| 94 |
-
);
|
| 95 |
});
|
| 96 |
|
| 97 |
const annotations = [];
|
| 98 |
|
| 99 |
-
rows.forEach((row
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
text:
|
| 112 |
-
value != null
|
| 113 |
-
? Number(value).toFixed(
|
| 114 |
-
2
|
| 115 |
-
)
|
| 116 |
-
: "—",
|
| 117 |
-
showarrow: false,
|
| 118 |
-
font: {
|
| 119 |
-
color: "white",
|
| 120 |
-
size: 13,
|
| 121 |
-
},
|
| 122 |
-
});
|
| 123 |
-
}
|
| 124 |
-
);
|
| 125 |
});
|
| 126 |
|
| 127 |
return (
|
|
@@ -129,68 +70,34 @@ export default function HeatmapView({
|
|
| 129 |
data={[
|
| 130 |
{
|
| 131 |
z,
|
| 132 |
-
x: METRICS.map(
|
| 133 |
-
|
| 134 |
-
),
|
| 135 |
-
y: rows.map(
|
| 136 |
-
(r) =>
|
| 137 |
-
r.config
|
| 138 |
-
?.replaceAll(
|
| 139 |
-
"_",
|
| 140 |
-
" "
|
| 141 |
-
)
|
| 142 |
-
?.replaceAll(
|
| 143 |
-
"+",
|
| 144 |
-
" + "
|
| 145 |
-
) ?? ""
|
| 146 |
-
),
|
| 147 |
type: "heatmap",
|
| 148 |
colorscale: "RdYlGn",
|
| 149 |
reversescale: false,
|
| 150 |
showscale: true,
|
| 151 |
hoverongaps: false,
|
| 152 |
-
hovertemplate:
|
| 153 |
-
"<b>%{y}</b><br>%{x}<br>%{z:.2f}<extra></extra>",
|
| 154 |
},
|
| 155 |
]}
|
| 156 |
layout={{
|
| 157 |
-
height:
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
r: 50,
|
| 163 |
-
t: 30,
|
| 164 |
-
b: 70,
|
| 165 |
-
},
|
| 166 |
-
paper_bgcolor:
|
| 167 |
-
"rgba(0,0,0,0)",
|
| 168 |
-
plot_bgcolor:
|
| 169 |
-
"rgba(0,0,0,0)",
|
| 170 |
-
font: {
|
| 171 |
-
color: "white",
|
| 172 |
-
},
|
| 173 |
annotations,
|
| 174 |
xaxis: {
|
| 175 |
side: "top",
|
| 176 |
-
tickfont: {
|
| 177 |
-
size: 14,
|
| 178 |
-
},
|
| 179 |
},
|
| 180 |
yaxis: {
|
| 181 |
-
tickfont: {
|
| 182 |
-
size: 14,
|
| 183 |
-
},
|
| 184 |
autorange: "reversed",
|
| 185 |
},
|
| 186 |
}}
|
| 187 |
-
style={{
|
| 188 |
-
|
| 189 |
-
}}
|
| 190 |
-
config={{
|
| 191 |
-
displayModeBar: false,
|
| 192 |
-
responsive: true,
|
| 193 |
-
}}
|
| 194 |
/>
|
| 195 |
);
|
| 196 |
}
|
|
|
|
| 1 |
import Plot from "react-plotly.js";
|
| 2 |
+
import { parseConfigDetails } from "../../utils/configUtils";
|
| 3 |
|
| 4 |
const METRICS = [
|
| 5 |
+
{ key: "faithfulness", label: "Faithfulness" },
|
| 6 |
+
{ key: "grounding", label: "Grounding" },
|
| 7 |
+
{ key: "quality", label: "Quality" },
|
| 8 |
+
{ key: "coverage", label: "Coverage" },
|
| 9 |
+
{ key: "latency", label: "Latency" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
];
|
| 11 |
|
| 12 |
+
const LOWER_IS_BETTER = new Set(["latency"]);
|
|
|
|
|
|
|
| 13 |
|
| 14 |
function normalizeColumn(values, invert = false) {
|
| 15 |
+
const valid = values.filter((v) => v != null && !Number.isNaN(v));
|
|
|
|
|
|
|
| 16 |
|
| 17 |
if (!valid.length) {
|
| 18 |
return values.map(() => null);
|
|
|
|
| 24 |
|
| 25 |
return values.map((v) => {
|
| 26 |
if (v == null) return null;
|
| 27 |
+
let score = range === 0 ? 1 : (v - min) / range;
|
| 28 |
+
if (invert) score = 1 - score;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
return score;
|
| 30 |
});
|
| 31 |
}
|
| 32 |
|
| 33 |
+
export default function HeatmapView({ data }) {
|
|
|
|
|
|
|
| 34 |
const rows = data ?? [];
|
| 35 |
|
| 36 |
if (!rows.length) {
|
| 37 |
return (
|
| 38 |
+
<p style={{ color: "rgba(255,255,255,0.4)", margin: 0 }}>
|
| 39 |
+
No configurations to compare yet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
</p>
|
| 41 |
);
|
| 42 |
}
|
| 43 |
|
| 44 |
const z = rows.map((row, i) => {
|
| 45 |
+
return METRICS.map(({ key }) => {
|
| 46 |
+
const values = rows.map((r) => r[key]);
|
| 47 |
+
const normalized = normalizeColumn(values, LOWER_IS_BETTER.has(key));
|
| 48 |
+
return normalized[i];
|
| 49 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
});
|
| 51 |
|
| 52 |
const annotations = [];
|
| 53 |
|
| 54 |
+
rows.forEach((row) => {
|
| 55 |
+
const cLabel = row.configLabel || row.config;
|
| 56 |
+
METRICS.forEach(({ key, label }) => {
|
| 57 |
+
const value = row[key];
|
| 58 |
+
annotations.push({
|
| 59 |
+
x: label,
|
| 60 |
+
y: cLabel,
|
| 61 |
+
text: value != null ? Number(value).toFixed(2) : "—",
|
| 62 |
+
showarrow: false,
|
| 63 |
+
font: { color: "white", size: 13 },
|
| 64 |
+
});
|
| 65 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
});
|
| 67 |
|
| 68 |
return (
|
|
|
|
| 70 |
data={[
|
| 71 |
{
|
| 72 |
z,
|
| 73 |
+
x: METRICS.map((m) => m.label),
|
| 74 |
+
y: rows.map((r) => r.configLabel || r.config),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
type: "heatmap",
|
| 76 |
colorscale: "RdYlGn",
|
| 77 |
reversescale: false,
|
| 78 |
showscale: true,
|
| 79 |
hoverongaps: false,
|
| 80 |
+
hovertemplate: "<b>%{y}</b><br>%{x}: %{z:.2f}<extra></extra>",
|
|
|
|
| 81 |
},
|
| 82 |
]}
|
| 83 |
layout={{
|
| 84 |
+
height: 120 + rows.length * 60,
|
| 85 |
+
margin: { l: 120, r: 50, t: 30, b: 60 },
|
| 86 |
+
paper_bgcolor: "rgba(0,0,0,0)",
|
| 87 |
+
plot_bgcolor: "rgba(0,0,0,0)",
|
| 88 |
+
font: { color: "white" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
annotations,
|
| 90 |
xaxis: {
|
| 91 |
side: "top",
|
| 92 |
+
tickfont: { size: 13, color: "rgba(255,255,255,0.85)" },
|
|
|
|
|
|
|
| 93 |
},
|
| 94 |
yaxis: {
|
| 95 |
+
tickfont: { size: 13, color: "rgba(255,255,255,0.85)" },
|
|
|
|
|
|
|
| 96 |
autorange: "reversed",
|
| 97 |
},
|
| 98 |
}}
|
| 99 |
+
style={{ width: "100%" }}
|
| 100 |
+
config={{ displayModeBar: false, responsive: true }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
/>
|
| 102 |
);
|
| 103 |
}
|
frontend/src/gaugepilot/components/visualizations/ParallelCoordinatesView.jsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
| 9 |
Legend,
|
| 10 |
ResponsiveContainer,
|
| 11 |
} from "recharts";
|
|
|
|
| 12 |
|
| 13 |
const METRICS = [
|
| 14 |
{ key: "faithfulness", label: "Faithfulness" },
|
|
@@ -20,8 +21,7 @@ const METRICS = [
|
|
| 20 |
|
| 21 |
const LOWER_IS_BETTER = new Set(["latency"]);
|
| 22 |
|
| 23 |
-
const getColor = (index) =>
|
| 24 |
-
`hsl(${(index * 137.508) % 360}, 70%, 55%)`;
|
| 25 |
|
| 26 |
function buildNormalizers(rows) {
|
| 27 |
const map = {};
|
|
@@ -42,14 +42,8 @@ function buildNormalizers(rows) {
|
|
| 42 |
|
| 43 |
map[key] = (v) => {
|
| 44 |
if (v == null) return null;
|
| 45 |
-
|
| 46 |
-
const
|
| 47 |
-
range === 0 ? 1 : (v - min) / range;
|
| 48 |
-
|
| 49 |
-
const goodness = LOWER_IS_BETTER.has(key)
|
| 50 |
-
? 1 - t
|
| 51 |
-
: t;
|
| 52 |
-
|
| 53 |
return Math.round(goodness * 100);
|
| 54 |
};
|
| 55 |
});
|
|
@@ -57,42 +51,26 @@ function buildNormalizers(rows) {
|
|
| 57 |
return map;
|
| 58 |
}
|
| 59 |
|
| 60 |
-
export default function ParallelCoordinatesView({
|
| 61 |
-
data,
|
| 62 |
-
}) {
|
| 63 |
const rows = data ?? [];
|
| 64 |
|
| 65 |
const displayRows = useMemo(() => {
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
return [...rows]
|
| 71 |
-
.sort(
|
| 72 |
-
(a, b) =>
|
| 73 |
-
(a.average_rank ?? 999) -
|
| 74 |
-
(b.average_rank ?? 999)
|
| 75 |
-
)
|
| 76 |
-
.slice(0, 7);
|
| 77 |
}, [rows]);
|
| 78 |
|
| 79 |
-
const normalizers = useMemo(
|
| 80 |
-
() => buildNormalizers(displayRows),
|
| 81 |
-
[displayRows]
|
| 82 |
-
);
|
| 83 |
|
| 84 |
const chartData = useMemo(
|
| 85 |
() =>
|
| 86 |
METRICS.map(({ key, label }) => {
|
| 87 |
-
const point = {
|
| 88 |
-
metric: label,
|
| 89 |
-
};
|
| 90 |
-
|
| 91 |
displayRows.forEach((row) => {
|
| 92 |
-
|
| 93 |
-
|
| 94 |
});
|
| 95 |
-
|
| 96 |
return point;
|
| 97 |
}),
|
| 98 |
[displayRows, normalizers]
|
|
@@ -100,168 +78,106 @@ export default function ParallelCoordinatesView({
|
|
| 100 |
|
| 101 |
const rawByConfigAndMetric = useMemo(() => {
|
| 102 |
const map = {};
|
| 103 |
-
|
| 104 |
displayRows.forEach((row) => {
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
| 107 |
METRICS.forEach(({ key }) => {
|
| 108 |
-
map[
|
| 109 |
});
|
| 110 |
});
|
| 111 |
-
|
| 112 |
return map;
|
| 113 |
}, [displayRows]);
|
| 114 |
|
| 115 |
-
const CustomTooltip = ({
|
| 116 |
-
active
|
| 117 |
-
payload,
|
| 118 |
-
label,
|
| 119 |
-
}) => {
|
| 120 |
-
if (!active || !payload?.length) {
|
| 121 |
-
return null;
|
| 122 |
-
}
|
| 123 |
|
| 124 |
-
const metricKey = METRICS.find(
|
| 125 |
-
(m) => m.label === label
|
| 126 |
-
)?.key;
|
| 127 |
|
| 128 |
return (
|
| 129 |
<div
|
| 130 |
style={{
|
| 131 |
-
background:
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
borderRadius: "12px",
|
| 136 |
-
padding: "12px 14px",
|
| 137 |
fontSize: "12px",
|
| 138 |
-
minWidth: "
|
|
|
|
| 139 |
}}
|
| 140 |
>
|
| 141 |
-
<div
|
| 142 |
-
|
| 143 |
-
color: "white",
|
| 144 |
-
fontWeight: 700,
|
| 145 |
-
marginBottom: "8px",
|
| 146 |
-
}}
|
| 147 |
-
>
|
| 148 |
-
{label}
|
| 149 |
</div>
|
| 150 |
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
justifyContent: "space-between",
|
| 157 |
-
gap: "12px",
|
| 158 |
-
color: entry.color,
|
| 159 |
-
marginBottom: "4px",
|
| 160 |
-
}}
|
| 161 |
-
>
|
| 162 |
-
<span>
|
| 163 |
-
{String(entry.dataKey).replaceAll(
|
| 164 |
-
"_",
|
| 165 |
-
" "
|
| 166 |
-
)}
|
| 167 |
-
</span>
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
entry.dataKey
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
</div>
|
| 183 |
);
|
| 184 |
};
|
| 185 |
|
| 186 |
-
if (
|
| 187 |
return (
|
| 188 |
-
<p
|
| 189 |
-
|
| 190 |
-
margin: 0,
|
| 191 |
-
fontSize: "13px",
|
| 192 |
-
color:
|
| 193 |
-
"rgba(255,255,255,0.35)",
|
| 194 |
-
}}
|
| 195 |
-
>
|
| 196 |
-
No configurations to compare yet.
|
| 197 |
</p>
|
| 198 |
);
|
| 199 |
}
|
| 200 |
|
| 201 |
return (
|
| 202 |
<div>
|
| 203 |
-
|
| 204 |
-
<
|
| 205 |
-
|
| 206 |
-
marginBottom: 14,
|
| 207 |
-
fontSize: 12,
|
| 208 |
-
color:
|
| 209 |
-
"rgba(255,255,255,0.45)",
|
| 210 |
-
}}
|
| 211 |
-
>
|
| 212 |
-
Showing top 7 of {rows.length} configurations.
|
| 213 |
-
</div>
|
| 214 |
-
)}
|
| 215 |
-
|
| 216 |
-
<ResponsiveContainer
|
| 217 |
-
width="100%"
|
| 218 |
-
height={450}
|
| 219 |
-
>
|
| 220 |
-
<LineChart
|
| 221 |
-
data={chartData}
|
| 222 |
-
margin={{
|
| 223 |
-
top: 20,
|
| 224 |
-
right: 30,
|
| 225 |
-
bottom: 20,
|
| 226 |
-
left: 10,
|
| 227 |
-
}}
|
| 228 |
-
>
|
| 229 |
-
<CartesianGrid
|
| 230 |
-
stroke="rgba(255,255,255,0.06)"
|
| 231 |
-
/>
|
| 232 |
-
|
| 233 |
<XAxis
|
| 234 |
dataKey="metric"
|
| 235 |
stroke="rgba(255,255,255,0.35)"
|
| 236 |
-
tick={{
|
| 237 |
-
fill:
|
| 238 |
-
"rgba(255,255,255,0.6)",
|
| 239 |
-
fontSize: 12,
|
| 240 |
-
}}
|
| 241 |
/>
|
| 242 |
-
|
| 243 |
<YAxis
|
| 244 |
domain={[0, 100]}
|
| 245 |
stroke="rgba(255,255,255,0.35)"
|
| 246 |
-
tick={{
|
| 247 |
-
fill:
|
| 248 |
-
"rgba(255,255,255,0.6)",
|
| 249 |
-
fontSize: 11,
|
| 250 |
-
}}
|
| 251 |
label={{
|
| 252 |
value: "Normalized Score",
|
| 253 |
angle: -90,
|
| 254 |
position: "insideLeft",
|
| 255 |
-
fill:
|
| 256 |
-
"rgba(255,255,255,0.45)",
|
| 257 |
fontSize: 11,
|
| 258 |
}}
|
| 259 |
/>
|
| 260 |
-
|
| 261 |
-
<Tooltip
|
| 262 |
-
content={<CustomTooltip />}
|
| 263 |
-
/>
|
| 264 |
-
|
| 265 |
<Legend
|
| 266 |
layout="horizontal"
|
| 267 |
verticalAlign="bottom"
|
|
@@ -269,63 +185,35 @@ export default function ParallelCoordinatesView({
|
|
| 269 |
maxHeight: 70,
|
| 270 |
overflowY: "auto",
|
| 271 |
fontSize: 12,
|
|
|
|
| 272 |
}}
|
| 273 |
formatter={(value) => (
|
| 274 |
-
<span
|
| 275 |
-
|
| 276 |
-
color:
|
| 277 |
-
"rgba(255,255,255,0.7)",
|
| 278 |
-
}}
|
| 279 |
-
>
|
| 280 |
-
{value.replaceAll(
|
| 281 |
-
"_",
|
| 282 |
-
" "
|
| 283 |
-
)}
|
| 284 |
</span>
|
| 285 |
)}
|
| 286 |
/>
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
(a, b) =>
|
| 291 |
-
(a.average_rank ?? 999) -
|
| 292 |
-
(b.average_rank ?? 999)
|
| 293 |
-
)
|
| 294 |
-
.map((row, i) => (
|
| 295 |
<Line
|
| 296 |
-
key={
|
| 297 |
type="monotone"
|
| 298 |
-
name={
|
| 299 |
-
dataKey={
|
| 300 |
stroke={getColor(i)}
|
| 301 |
strokeWidth={3}
|
| 302 |
-
dot={{
|
| 303 |
-
|
| 304 |
-
fill: getColor(i),
|
| 305 |
-
strokeWidth: 2,
|
| 306 |
-
}}
|
| 307 |
-
activeDot={{
|
| 308 |
-
r: 7,
|
| 309 |
-
}}
|
| 310 |
connectNulls
|
| 311 |
/>
|
| 312 |
-
)
|
|
|
|
| 313 |
</LineChart>
|
| 314 |
</ResponsiveContainer>
|
| 315 |
|
| 316 |
-
<p
|
| 317 |
-
|
| 318 |
-
margin: "16px 0 0",
|
| 319 |
-
fontSize: "11px",
|
| 320 |
-
color:
|
| 321 |
-
"rgba(255,255,255,0.3)",
|
| 322 |
-
}}
|
| 323 |
-
>
|
| 324 |
-
Each metric is normalized from
|
| 325 |
-
0–100 across all displayed
|
| 326 |
-
configurations. Latency is
|
| 327 |
-
inverted so higher always means
|
| 328 |
-
better.
|
| 329 |
</p>
|
| 330 |
</div>
|
| 331 |
);
|
|
|
|
| 9 |
Legend,
|
| 10 |
ResponsiveContainer,
|
| 11 |
} from "recharts";
|
| 12 |
+
import { parseConfigDetails } from "../../utils/configUtils";
|
| 13 |
|
| 14 |
const METRICS = [
|
| 15 |
{ key: "faithfulness", label: "Faithfulness" },
|
|
|
|
| 21 |
|
| 22 |
const LOWER_IS_BETTER = new Set(["latency"]);
|
| 23 |
|
| 24 |
+
const getColor = (index) => `hsl(${(index * 137.508) % 360}, 70%, 55%)`;
|
|
|
|
| 25 |
|
| 26 |
function buildNormalizers(rows) {
|
| 27 |
const map = {};
|
|
|
|
| 42 |
|
| 43 |
map[key] = (v) => {
|
| 44 |
if (v == null) return null;
|
| 45 |
+
const t = range === 0 ? 1 : (v - min) / range;
|
| 46 |
+
const goodness = LOWER_IS_BETTER.has(key) ? 1 - t : t;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
return Math.round(goodness * 100);
|
| 48 |
};
|
| 49 |
});
|
|
|
|
| 51 |
return map;
|
| 52 |
}
|
| 53 |
|
| 54 |
+
export default function ParallelCoordinatesView({ data }) {
|
|
|
|
|
|
|
| 55 |
const rows = data ?? [];
|
| 56 |
|
| 57 |
const displayRows = useMemo(() => {
|
| 58 |
+
const sorted = [...rows].sort(
|
| 59 |
+
(a, b) => (a.averageRank ?? 999) - (b.averageRank ?? 999)
|
| 60 |
+
);
|
| 61 |
+
return sorted.slice(0, 8);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
}, [rows]);
|
| 63 |
|
| 64 |
+
const normalizers = useMemo(() => buildNormalizers(displayRows), [displayRows]);
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
const chartData = useMemo(
|
| 67 |
() =>
|
| 68 |
METRICS.map(({ key, label }) => {
|
| 69 |
+
const point = { metric: label };
|
|
|
|
|
|
|
|
|
|
| 70 |
displayRows.forEach((row) => {
|
| 71 |
+
const cLabel = row.configLabel || row.config;
|
| 72 |
+
point[cLabel] = normalizers[key](row[key]) ?? null;
|
| 73 |
});
|
|
|
|
| 74 |
return point;
|
| 75 |
}),
|
| 76 |
[displayRows, normalizers]
|
|
|
|
| 78 |
|
| 79 |
const rawByConfigAndMetric = useMemo(() => {
|
| 80 |
const map = {};
|
|
|
|
| 81 |
displayRows.forEach((row) => {
|
| 82 |
+
const cLabel = row.configLabel || row.config;
|
| 83 |
+
map[cLabel] = {
|
| 84 |
+
details: row.configDetails || parseConfigDetails(row.config),
|
| 85 |
+
metrics: {},
|
| 86 |
+
};
|
| 87 |
METRICS.forEach(({ key }) => {
|
| 88 |
+
map[cLabel].metrics[key] = row[key];
|
| 89 |
});
|
| 90 |
});
|
|
|
|
| 91 |
return map;
|
| 92 |
}, [displayRows]);
|
| 93 |
|
| 94 |
+
const CustomTooltip = ({ active, payload, label }) => {
|
| 95 |
+
if (!active || !payload?.length) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
+
const metricKey = METRICS.find((m) => m.label === label)?.key;
|
|
|
|
|
|
|
| 98 |
|
| 99 |
return (
|
| 100 |
<div
|
| 101 |
style={{
|
| 102 |
+
background: "rgba(15, 20, 36, 0.97)",
|
| 103 |
+
border: "1px solid rgba(168, 85, 247, 0.35)",
|
| 104 |
+
borderRadius: "14px",
|
| 105 |
+
padding: "12px 16px",
|
|
|
|
|
|
|
| 106 |
fontSize: "12px",
|
| 107 |
+
minWidth: "260px",
|
| 108 |
+
boxShadow: "0 16px 36px rgba(0,0,0,0.6)",
|
| 109 |
}}
|
| 110 |
>
|
| 111 |
+
<div style={{ color: "white", fontWeight: 700, marginBottom: 10, fontSize: "13px", borderBottom: "1px solid rgba(255,255,255,0.08)", paddingBottom: "6px" }}>
|
| 112 |
+
📊 {label} Coordinate Trajectory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
</div>
|
| 114 |
|
| 115 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
| 116 |
+
{payload.map((entry) => {
|
| 117 |
+
const info = rawByConfigAndMetric[entry.dataKey];
|
| 118 |
+
const rawVal = info?.metrics?.[metricKey];
|
| 119 |
+
const details = info?.details;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
+
return (
|
| 122 |
+
<div
|
| 123 |
+
key={entry.dataKey}
|
| 124 |
+
style={{
|
| 125 |
+
padding: "6px 8px",
|
| 126 |
+
borderRadius: "8px",
|
| 127 |
+
background: "rgba(255,255,255,0.03)",
|
| 128 |
+
borderLeft: `3px solid ${entry.color}`,
|
| 129 |
+
}}
|
| 130 |
+
>
|
| 131 |
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 132 |
+
<span style={{ color: entry.color, fontWeight: 700 }}>{entry.dataKey}</span>
|
| 133 |
+
<span style={{ color: "rgba(255,255,255,0.9)", fontWeight: 600 }}>
|
| 134 |
+
Score: {entry.value}/100 {rawVal != null ? `(${Number(rawVal).toFixed(2)})` : ""}
|
| 135 |
+
</span>
|
| 136 |
+
</div>
|
| 137 |
+
{details && (
|
| 138 |
+
<div style={{ fontSize: "10.5px", color: "rgba(255,255,255,0.6)", marginTop: "2px" }}>
|
| 139 |
+
{details.model} · {details.retrieval} · {details.reranker}
|
| 140 |
+
</div>
|
| 141 |
+
)}
|
| 142 |
+
</div>
|
| 143 |
+
);
|
| 144 |
+
})}
|
| 145 |
+
</div>
|
| 146 |
</div>
|
| 147 |
);
|
| 148 |
};
|
| 149 |
|
| 150 |
+
if (displayRows.length === 0) {
|
| 151 |
return (
|
| 152 |
+
<p style={{ margin: 0, fontSize: "13px", color: "rgba(255,255,255,0.35)" }}>
|
| 153 |
+
No configurations to plot yet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
</p>
|
| 155 |
);
|
| 156 |
}
|
| 157 |
|
| 158 |
return (
|
| 159 |
<div>
|
| 160 |
+
<ResponsiveContainer width="100%" height={380}>
|
| 161 |
+
<LineChart data={chartData} margin={{ top: 20, right: 30, bottom: 20, left: 10 }}>
|
| 162 |
+
<CartesianGrid stroke="rgba(255,255,255,0.06)" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
<XAxis
|
| 164 |
dataKey="metric"
|
| 165 |
stroke="rgba(255,255,255,0.35)"
|
| 166 |
+
tick={{ fill: "rgba(255,255,255,0.85)", fontSize: 12, fontWeight: 600 }}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
/>
|
|
|
|
| 168 |
<YAxis
|
| 169 |
domain={[0, 100]}
|
| 170 |
stroke="rgba(255,255,255,0.35)"
|
| 171 |
+
tick={{ fill: "rgba(255,255,255,0.5)", fontSize: 11 }}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
label={{
|
| 173 |
value: "Normalized Score",
|
| 174 |
angle: -90,
|
| 175 |
position: "insideLeft",
|
| 176 |
+
fill: "rgba(255,255,255,0.45)",
|
|
|
|
| 177 |
fontSize: 11,
|
| 178 |
}}
|
| 179 |
/>
|
| 180 |
+
<Tooltip content={<CustomTooltip />} />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
<Legend
|
| 182 |
layout="horizontal"
|
| 183 |
verticalAlign="bottom"
|
|
|
|
| 185 |
maxHeight: 70,
|
| 186 |
overflowY: "auto",
|
| 187 |
fontSize: 12,
|
| 188 |
+
paddingTop: "10px",
|
| 189 |
}}
|
| 190 |
formatter={(value) => (
|
| 191 |
+
<span style={{ color: "rgba(255,255,255,0.85)", fontWeight: 600, marginRight: 8 }}>
|
| 192 |
+
{value}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
</span>
|
| 194 |
)}
|
| 195 |
/>
|
| 196 |
+
{displayRows.map((row, i) => {
|
| 197 |
+
const cLabel = row.configLabel || row.config;
|
| 198 |
+
return (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
<Line
|
| 200 |
+
key={cLabel}
|
| 201 |
type="monotone"
|
| 202 |
+
name={cLabel}
|
| 203 |
+
dataKey={cLabel}
|
| 204 |
stroke={getColor(i)}
|
| 205 |
strokeWidth={3}
|
| 206 |
+
dot={{ r: 5, fill: getColor(i), strokeWidth: 2 }}
|
| 207 |
+
activeDot={{ r: 7 }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
connectNulls
|
| 209 |
/>
|
| 210 |
+
);
|
| 211 |
+
})}
|
| 212 |
</LineChart>
|
| 213 |
</ResponsiveContainer>
|
| 214 |
|
| 215 |
+
<p style={{ margin: "16px 0 0", fontSize: "11px", color: "rgba(255,255,255,0.3)" }}>
|
| 216 |
+
Each metric is normalized from 0–100 across displayed configurations. Latency is inverted so higher represents better performance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
</p>
|
| 218 |
</div>
|
| 219 |
);
|
frontend/src/gaugepilot/components/visualizations/ParetoView.jsx
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
| 3 |
ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
|
| 4 |
} from "recharts";
|
| 5 |
import ExperimentSelector from "../ExperimentSelector";
|
|
|
|
| 6 |
|
| 7 |
const METRICS = [
|
| 8 |
{ value: "faithfulness", label: "Faithfulness" },
|
|
@@ -16,7 +17,6 @@ const LOWER_IS_BETTER = new Set(["latency"]);
|
|
| 16 |
const metricLabel = (key) => METRICS.find((m) => m.value === key)?.label ?? key;
|
| 17 |
|
| 18 |
function computeFrontier(points, xMetric, yMetric) {
|
| 19 |
-
// Orient both axes so "higher is better" for the dominance comparison.
|
| 20 |
const oriented = points.map((p) => ({
|
| 21 |
...p,
|
| 22 |
ox: LOWER_IS_BETTER.has(xMetric) ? -p.x : p.x,
|
|
@@ -42,7 +42,13 @@ export default function ParetoView({ data }) {
|
|
| 42 |
() =>
|
| 43 |
(data ?? [])
|
| 44 |
.filter((row) => row[xMetric] != null && row[yMetric] != null)
|
| 45 |
-
.map((row) => ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
[data, xMetric, yMetric]
|
| 47 |
);
|
| 48 |
|
|
@@ -56,22 +62,41 @@ export default function ParetoView({ data }) {
|
|
| 56 |
const CustomTooltip = ({ active, payload }) => {
|
| 57 |
if (!active || !payload?.length) return null;
|
| 58 |
const p = payload[0].payload;
|
|
|
|
|
|
|
| 59 |
return (
|
| 60 |
<div style={{
|
| 61 |
-
background: "rgba(15,
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
}}>
|
| 64 |
-
<div style={{ fontWeight: 700, color: "white", marginBottom: "
|
| 65 |
-
{p.
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
</div>
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
</div>
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
| 75 |
</div>
|
| 76 |
</div>
|
| 77 |
);
|
|
@@ -108,18 +133,14 @@ export default function ParetoView({ data }) {
|
|
| 108 |
<Legend
|
| 109 |
verticalAlign="top" align="right" height={30}
|
| 110 |
payload={[
|
| 111 |
-
{ value: "Pareto
|
| 112 |
{ value: "Dominated", type: "circle", color: muted },
|
| 113 |
]}
|
| 114 |
-
formatter={(value) => <span style={{ color: "rgba(255,255,255,0.6)", fontSize: 12 }}>{value}</span>}
|
| 115 |
/>
|
| 116 |
-
<Scatter data={dominated} fill={muted}
|
| 117 |
-
<Scatter data={frontier} fill=
|
| 118 |
</ScatterChart>
|
| 119 |
</ResponsiveContainer>
|
| 120 |
-
<p style={{ margin: "14px 0 0", fontSize: "11px", color: "rgba(255,255,255,0.3)" }}>
|
| 121 |
-
Dashed line connects Pareto-optimal configurations; dominated configurations are faded.
|
| 122 |
-
</p>
|
| 123 |
</div>
|
| 124 |
);
|
| 125 |
}
|
|
|
|
| 3 |
ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
|
| 4 |
} from "recharts";
|
| 5 |
import ExperimentSelector from "../ExperimentSelector";
|
| 6 |
+
import { parseConfigDetails } from "../../utils/configUtils";
|
| 7 |
|
| 8 |
const METRICS = [
|
| 9 |
{ value: "faithfulness", label: "Faithfulness" },
|
|
|
|
| 17 |
const metricLabel = (key) => METRICS.find((m) => m.value === key)?.label ?? key;
|
| 18 |
|
| 19 |
function computeFrontier(points, xMetric, yMetric) {
|
|
|
|
| 20 |
const oriented = points.map((p) => ({
|
| 21 |
...p,
|
| 22 |
ox: LOWER_IS_BETTER.has(xMetric) ? -p.x : p.x,
|
|
|
|
| 42 |
() =>
|
| 43 |
(data ?? [])
|
| 44 |
.filter((row) => row[xMetric] != null && row[yMetric] != null)
|
| 45 |
+
.map((row) => ({
|
| 46 |
+
config: row.config,
|
| 47 |
+
configLabel: row.configLabel || row.config,
|
| 48 |
+
configDetails: row.configDetails || parseConfigDetails(row.config),
|
| 49 |
+
x: row[xMetric],
|
| 50 |
+
y: row[yMetric],
|
| 51 |
+
})),
|
| 52 |
[data, xMetric, yMetric]
|
| 53 |
);
|
| 54 |
|
|
|
|
| 62 |
const CustomTooltip = ({ active, payload }) => {
|
| 63 |
if (!active || !payload?.length) return null;
|
| 64 |
const p = payload[0].payload;
|
| 65 |
+
const details = p.configDetails || parseConfigDetails(p.config);
|
| 66 |
+
|
| 67 |
return (
|
| 68 |
<div style={{
|
| 69 |
+
background: "rgba(15, 20, 36, 0.97)",
|
| 70 |
+
border: "1px solid rgba(168, 85, 247, 0.35)",
|
| 71 |
+
borderRadius: "14px",
|
| 72 |
+
padding: "12px 16px",
|
| 73 |
+
fontSize: "12px",
|
| 74 |
+
boxShadow: "0 16px 36px rgba(0,0,0,0.6)",
|
| 75 |
+
maxWidth: "300px",
|
| 76 |
}}>
|
| 77 |
+
<div style={{ fontWeight: 700, color: "white", fontSize: "13px", marginBottom: "6px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 78 |
+
<span>🏷️ {p.configLabel}</span>
|
| 79 |
+
<span style={{
|
| 80 |
+
fontSize: "10px",
|
| 81 |
+
fontWeight: 700,
|
| 82 |
+
padding: "2px 6px",
|
| 83 |
+
borderRadius: "4px",
|
| 84 |
+
background: p.dominated ? "rgba(255,255,255,0.06)" : "rgba(34, 197, 94, 0.2)",
|
| 85 |
+
color: p.dominated ? "rgba(255,255,255,0.5)" : "#22c55e",
|
| 86 |
+
}}>
|
| 87 |
+
{p.dominated ? "Dominated" : "Pareto Optimal"}
|
| 88 |
+
</span>
|
| 89 |
</div>
|
| 90 |
+
|
| 91 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "4px", fontSize: "11.5px", color: "rgba(255,255,255,0.8)" }}>
|
| 92 |
+
<div>{metricLabel(xMetric)}: <strong style={{ color: "#38bdf8" }}>{Number(p.x).toFixed(2)}</strong></div>
|
| 93 |
+
<div>{metricLabel(yMetric)}: <strong style={{ color: "#c084fc" }}>{Number(p.y).toFixed(2)}</strong></div>
|
| 94 |
</div>
|
| 95 |
+
|
| 96 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "2px", fontSize: "10.5px", color: "rgba(255,255,255,0.6)", borderTop: "1px solid rgba(255,255,255,0.08)", paddingTop: "6px", marginTop: "6px" }}>
|
| 97 |
+
<div>Model: {details.model}</div>
|
| 98 |
+
<div>Retrieval: {details.retrieval} · {details.reranker}</div>
|
| 99 |
+
<div>Enhancements: {details.enhancements?.join(", ")}</div>
|
| 100 |
</div>
|
| 101 |
</div>
|
| 102 |
);
|
|
|
|
| 133 |
<Legend
|
| 134 |
verticalAlign="top" align="right" height={30}
|
| 135 |
payload={[
|
| 136 |
+
{ value: "Pareto Frontier (Optimal)", type: "circle", color: "#22c55e" },
|
| 137 |
{ value: "Dominated", type: "circle", color: muted },
|
| 138 |
]}
|
|
|
|
| 139 |
/>
|
| 140 |
+
<Scatter name="Dominated" data={dominated} fill={muted} />
|
| 141 |
+
<Scatter name="Pareto Optimal" data={frontier} fill="#22c55e" line={{ stroke: "#22c55e", strokeWidth: 1.5, strokeDasharray: "4 4" }} />
|
| 142 |
</ScatterChart>
|
| 143 |
</ResponsiveContainer>
|
|
|
|
|
|
|
|
|
|
| 144 |
</div>
|
| 145 |
);
|
| 146 |
}
|
frontend/src/gaugepilot/components/visualizations/PerformanceProfile.jsx
CHANGED
|
@@ -2,6 +2,7 @@ import { useMemo } from "react";
|
|
| 2 |
import {
|
| 3 |
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Dot,
|
| 4 |
} from "recharts";
|
|
|
|
| 5 |
|
| 6 |
const METRICS = [
|
| 7 |
{ key: "faithfulness", label: "Faithfulness" },
|
|
@@ -28,7 +29,12 @@ function computeWins(rows) {
|
|
| 28 |
});
|
| 29 |
|
| 30 |
return rows
|
| 31 |
-
.map((r) => ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
.sort((a, b) => b.wins - a.wins);
|
| 33 |
}
|
| 34 |
|
|
@@ -39,16 +45,28 @@ export default function PerformanceProfile({ data }) {
|
|
| 39 |
const CustomTooltip = ({ active, payload }) => {
|
| 40 |
if (!active || !payload?.length) return null;
|
| 41 |
const p = payload[0].payload;
|
|
|
|
|
|
|
| 42 |
return (
|
| 43 |
<div style={{
|
| 44 |
-
background: "rgba(15,
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}}>
|
| 47 |
-
<div style={{ fontWeight: 700, color: "white", marginBottom: "
|
| 48 |
-
{p.
|
|
|
|
|
|
|
|
|
|
| 49 |
</div>
|
| 50 |
-
<div style={{ color: "rgba(255,255,255,0.6)" }}>
|
| 51 |
-
|
|
|
|
|
|
|
| 52 |
</div>
|
| 53 |
</div>
|
| 54 |
);
|
|
@@ -73,8 +91,8 @@ export default function PerformanceProfile({ data }) {
|
|
| 73 |
<LineChart data={profile} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
| 74 |
<CartesianGrid stroke="rgba(255,255,255,0.06)" />
|
| 75 |
<XAxis
|
| 76 |
-
dataKey="
|
| 77 |
-
stroke="rgba(255,255,255,0.35)" tick={{ fill: "rgba(255,255,255,0.
|
| 78 |
interval={0} angle={-20} textAnchor="end" height={60}
|
| 79 |
/>
|
| 80 |
<YAxis
|
|
@@ -87,7 +105,7 @@ export default function PerformanceProfile({ data }) {
|
|
| 87 |
</LineChart>
|
| 88 |
</ResponsiveContainer>
|
| 89 |
<p style={{ margin: "14px 0 0", fontSize: "11px", color: "rgba(255,255,255,0.3)" }}>
|
| 90 |
-
Counts how many of the {METRICS.length} metrics each configuration leads on (latency counts a win for
|
| 91 |
</p>
|
| 92 |
</div>
|
| 93 |
);
|
|
|
|
| 2 |
import {
|
| 3 |
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Dot,
|
| 4 |
} from "recharts";
|
| 5 |
+
import { parseConfigDetails } from "../../utils/configUtils";
|
| 6 |
|
| 7 |
const METRICS = [
|
| 8 |
{ key: "faithfulness", label: "Faithfulness" },
|
|
|
|
| 29 |
});
|
| 30 |
|
| 31 |
return rows
|
| 32 |
+
.map((r) => ({
|
| 33 |
+
config: r.config,
|
| 34 |
+
configLabel: r.configLabel || r.config,
|
| 35 |
+
configDetails: r.configDetails || parseConfigDetails(r.config),
|
| 36 |
+
wins: wins[r.config] ?? 0,
|
| 37 |
+
}))
|
| 38 |
.sort((a, b) => b.wins - a.wins);
|
| 39 |
}
|
| 40 |
|
|
|
|
| 45 |
const CustomTooltip = ({ active, payload }) => {
|
| 46 |
if (!active || !payload?.length) return null;
|
| 47 |
const p = payload[0].payload;
|
| 48 |
+
const details = p.configDetails || parseConfigDetails(p.config);
|
| 49 |
+
|
| 50 |
return (
|
| 51 |
<div style={{
|
| 52 |
+
background: "rgba(15, 20, 36, 0.97)",
|
| 53 |
+
border: "1px solid rgba(168, 85, 247, 0.35)",
|
| 54 |
+
borderRadius: "14px",
|
| 55 |
+
padding: "12px 16px",
|
| 56 |
+
fontSize: "12px",
|
| 57 |
+
boxShadow: "0 16px 36px rgba(0,0,0,0.6)",
|
| 58 |
+
maxWidth: "300px",
|
| 59 |
}}>
|
| 60 |
+
<div style={{ fontWeight: 700, color: "white", fontSize: "13px", marginBottom: "6px" }}>
|
| 61 |
+
🏷️ {p.configLabel}
|
| 62 |
+
</div>
|
| 63 |
+
<div style={{ color: "#38bdf8", fontWeight: 600, marginBottom: "6px" }}>
|
| 64 |
+
Metrics Won Outright: {p.wins} / {METRICS.length}
|
| 65 |
</div>
|
| 66 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "2px", fontSize: "10.5px", color: "rgba(255,255,255,0.6)", borderTop: "1px solid rgba(255,255,255,0.08)", paddingTop: "6px" }}>
|
| 67 |
+
<div>Model: {details.model}</div>
|
| 68 |
+
<div>Retrieval: {details.retrieval} · {details.reranker}</div>
|
| 69 |
+
<div>Enhancements: {details.enhancements?.join(", ")}</div>
|
| 70 |
</div>
|
| 71 |
</div>
|
| 72 |
);
|
|
|
|
| 91 |
<LineChart data={profile} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
| 92 |
<CartesianGrid stroke="rgba(255,255,255,0.06)" />
|
| 93 |
<XAxis
|
| 94 |
+
dataKey="configLabel"
|
| 95 |
+
stroke="rgba(255,255,255,0.35)" tick={{ fill: "rgba(255,255,255,0.75)", fontSize: 11, fontWeight: 600 }}
|
| 96 |
interval={0} angle={-20} textAnchor="end" height={60}
|
| 97 |
/>
|
| 98 |
<YAxis
|
|
|
|
| 105 |
</LineChart>
|
| 106 |
</ResponsiveContainer>
|
| 107 |
<p style={{ margin: "14px 0 0", fontSize: "11px", color: "rgba(255,255,255,0.3)" }}>
|
| 108 |
+
Counts how many of the {METRICS.length} metrics each configuration leads on (latency counts a win for lowest value).
|
| 109 |
</p>
|
| 110 |
</div>
|
| 111 |
);
|
frontend/src/gaugepilot/components/visualizations/RadarView.jsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
| 9 |
Tooltip,
|
| 10 |
Legend,
|
| 11 |
} from "recharts";
|
|
|
|
| 12 |
|
| 13 |
const METRICS = [
|
| 14 |
{ key: "faithfulness", label: "Faithfulness" },
|
|
@@ -20,8 +21,7 @@ const METRICS = [
|
|
| 20 |
|
| 21 |
const LOWER_IS_BETTER = new Set(["latency"]);
|
| 22 |
|
| 23 |
-
const getColor = (index) =>
|
| 24 |
-
`hsl(${(index * 137.508) % 360}, 70%, 55%)`;
|
| 25 |
|
| 26 |
function buildNormalizers(rows) {
|
| 27 |
const map = {};
|
|
@@ -41,271 +41,135 @@ function buildNormalizers(rows) {
|
|
| 41 |
const range = max - min;
|
| 42 |
|
| 43 |
map[key] = (v) => {
|
| 44 |
-
if (v == null)
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
const t =
|
| 49 |
-
range === 0
|
| 50 |
-
? 1
|
| 51 |
-
: (v - min) / range;
|
| 52 |
-
|
| 53 |
-
const goodness =
|
| 54 |
-
LOWER_IS_BETTER.has(key)
|
| 55 |
-
? 1 - t
|
| 56 |
-
: t;
|
| 57 |
-
|
| 58 |
-
return Math.round(
|
| 59 |
-
goodness * 100
|
| 60 |
-
);
|
| 61 |
};
|
| 62 |
});
|
| 63 |
|
| 64 |
return map;
|
| 65 |
}
|
| 66 |
|
| 67 |
-
export default function RadarView({
|
| 68 |
-
data,
|
| 69 |
-
}) {
|
| 70 |
const rows = data ?? [];
|
| 71 |
|
| 72 |
const displayRows = useMemo(() => {
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
return [...rows]
|
| 78 |
-
.sort(
|
| 79 |
-
(a, b) =>
|
| 80 |
-
(a.average_rank ?? 999) -
|
| 81 |
-
(b.average_rank ?? 999)
|
| 82 |
-
)
|
| 83 |
-
.slice(0, 7);
|
| 84 |
}, [rows]);
|
| 85 |
|
| 86 |
-
const normalizers = useMemo(
|
| 87 |
-
() =>
|
| 88 |
-
buildNormalizers(
|
| 89 |
-
displayRows
|
| 90 |
-
),
|
| 91 |
-
[displayRows]
|
| 92 |
-
);
|
| 93 |
-
|
| 94 |
-
const chartData = useMemo(
|
| 95 |
-
() =>
|
| 96 |
-
METRICS.map(
|
| 97 |
-
({ key, label }) => {
|
| 98 |
-
const point = {
|
| 99 |
-
metric: label,
|
| 100 |
-
};
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
}
|
| 113 |
-
),
|
| 114 |
-
[displayRows, normalizers]
|
| 115 |
-
);
|
| 116 |
|
| 117 |
const rawMap = useMemo(() => {
|
| 118 |
const map = {};
|
| 119 |
-
|
| 120 |
displayRows.forEach((row) => {
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
);
|
| 129 |
});
|
| 130 |
-
|
| 131 |
return map;
|
| 132 |
}, [displayRows]);
|
| 133 |
|
| 134 |
-
const CustomTooltip = ({
|
| 135 |
-
active
|
| 136 |
-
payload,
|
| 137 |
-
label,
|
| 138 |
-
}) => {
|
| 139 |
-
if (
|
| 140 |
-
!active ||
|
| 141 |
-
!payload?.length
|
| 142 |
-
) {
|
| 143 |
-
return null;
|
| 144 |
-
}
|
| 145 |
|
| 146 |
-
const metricKey =
|
| 147 |
-
METRICS.find(
|
| 148 |
-
(m) =>
|
| 149 |
-
m.label === label
|
| 150 |
-
)?.key;
|
| 151 |
|
| 152 |
return (
|
| 153 |
<div
|
| 154 |
style={{
|
| 155 |
-
background:
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
borderRadius: 12,
|
| 160 |
-
padding:
|
| 161 |
-
"12px 14px",
|
| 162 |
fontSize: 12,
|
| 163 |
-
minWidth:
|
|
|
|
| 164 |
}}
|
| 165 |
>
|
| 166 |
-
<div
|
| 167 |
-
|
| 168 |
-
color: "white",
|
| 169 |
-
fontWeight: 700,
|
| 170 |
-
marginBottom: 10,
|
| 171 |
-
}}
|
| 172 |
-
>
|
| 173 |
-
{label}
|
| 174 |
</div>
|
| 175 |
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
justifyContent:
|
| 182 |
-
"space-between",
|
| 183 |
-
gap: 14,
|
| 184 |
-
marginBottom: 6,
|
| 185 |
-
}}
|
| 186 |
-
>
|
| 187 |
-
<div
|
| 188 |
-
style={{
|
| 189 |
-
color:
|
| 190 |
-
entry.color,
|
| 191 |
-
}}
|
| 192 |
-
>
|
| 193 |
-
{entry.dataKey}
|
| 194 |
-
</div>
|
| 195 |
-
|
| 196 |
-
<div
|
| 197 |
-
style={{
|
| 198 |
-
textAlign:
|
| 199 |
-
"right",
|
| 200 |
-
color:
|
| 201 |
-
"rgba(255,255,255,0.9)",
|
| 202 |
-
}}
|
| 203 |
-
>
|
| 204 |
-
<div>
|
| 205 |
-
Score:{" "}
|
| 206 |
-
{entry.value}
|
| 207 |
-
</div>
|
| 208 |
|
|
|
|
| 209 |
<div
|
|
|
|
| 210 |
style={{
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
|
|
|
| 214 |
}}
|
| 215 |
>
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
metricKey
|
| 228 |
-
]
|
| 229 |
-
).toFixed(
|
| 230 |
-
2
|
| 231 |
-
)
|
| 232 |
-
: "—"}
|
| 233 |
</div>
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
</div>
|
| 238 |
);
|
| 239 |
};
|
| 240 |
|
| 241 |
-
if (
|
| 242 |
return (
|
| 243 |
-
<p
|
| 244 |
-
|
| 245 |
-
margin: 0,
|
| 246 |
-
fontSize: 13,
|
| 247 |
-
color:
|
| 248 |
-
"rgba(255,255,255,0.35)",
|
| 249 |
-
}}
|
| 250 |
-
>
|
| 251 |
-
No configurations to
|
| 252 |
-
profile yet.
|
| 253 |
</p>
|
| 254 |
);
|
| 255 |
}
|
| 256 |
|
| 257 |
return (
|
| 258 |
<div>
|
| 259 |
-
|
| 260 |
-
<
|
| 261 |
-
|
| 262 |
-
marginBottom: 14,
|
| 263 |
-
fontSize: 12,
|
| 264 |
-
color:
|
| 265 |
-
"rgba(255,255,255,0.45)",
|
| 266 |
-
}}
|
| 267 |
-
>
|
| 268 |
-
Showing top 7 of{" "}
|
| 269 |
-
{rows.length} configurations.
|
| 270 |
-
</div>
|
| 271 |
-
)}
|
| 272 |
-
|
| 273 |
-
<ResponsiveContainer
|
| 274 |
-
width="100%"
|
| 275 |
-
height={550}
|
| 276 |
-
>
|
| 277 |
-
<RadarChart
|
| 278 |
-
data={chartData}
|
| 279 |
-
outerRadius="82%"
|
| 280 |
-
>
|
| 281 |
-
<PolarGrid
|
| 282 |
-
stroke="rgba(255,255,255,0.08)"
|
| 283 |
-
/>
|
| 284 |
-
|
| 285 |
<PolarAngleAxis
|
| 286 |
dataKey="metric"
|
| 287 |
-
tick={{
|
| 288 |
-
fill:
|
| 289 |
-
"rgba(255,255,255,0.7)",
|
| 290 |
-
fontSize: 13,
|
| 291 |
-
}}
|
| 292 |
/>
|
| 293 |
-
|
| 294 |
<PolarRadiusAxis
|
| 295 |
-
angle={
|
| 296 |
domain={[0, 100]}
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
"rgba(255,255,255,0.3)",
|
| 300 |
-
fontSize: 11,
|
| 301 |
-
}}
|
| 302 |
/>
|
| 303 |
|
| 304 |
-
<Tooltip
|
| 305 |
-
content={
|
| 306 |
-
<CustomTooltip />
|
| 307 |
-
}
|
| 308 |
-
/>
|
| 309 |
|
| 310 |
<Legend
|
| 311 |
layout="horizontal"
|
|
@@ -314,61 +178,34 @@ export default function RadarView({
|
|
| 314 |
fontSize: 12,
|
| 315 |
maxHeight: 70,
|
| 316 |
overflowY: "auto",
|
|
|
|
| 317 |
}}
|
| 318 |
-
formatter={(
|
| 319 |
-
|
| 320 |
-
) => (
|
| 321 |
-
<span
|
| 322 |
-
style={{
|
| 323 |
-
color:
|
| 324 |
-
"rgba(255,255,255,0.7)",
|
| 325 |
-
}}
|
| 326 |
-
>
|
| 327 |
{value}
|
| 328 |
</span>
|
| 329 |
)}
|
| 330 |
/>
|
| 331 |
|
| 332 |
-
{displayRows.map(
|
| 333 |
-
|
|
|
|
| 334 |
<Radar
|
| 335 |
-
key={
|
| 336 |
-
name={
|
| 337 |
-
|
| 338 |
-
}
|
| 339 |
-
dataKey={
|
| 340 |
-
row.config
|
| 341 |
-
}
|
| 342 |
-
stroke={getColor(
|
| 343 |
-
i
|
| 344 |
-
)}
|
| 345 |
fill={getColor(i)}
|
| 346 |
-
fillOpacity={
|
| 347 |
-
0.12
|
| 348 |
-
}
|
| 349 |
strokeWidth={2}
|
| 350 |
/>
|
| 351 |
-
)
|
| 352 |
-
)}
|
| 353 |
</RadarChart>
|
| 354 |
</ResponsiveContainer>
|
| 355 |
|
| 356 |
-
<p
|
| 357 |
-
|
| 358 |
-
margin:
|
| 359 |
-
"16px 0 0",
|
| 360 |
-
fontSize: 11,
|
| 361 |
-
color:
|
| 362 |
-
"rgba(255,255,255,0.3)",
|
| 363 |
-
}}
|
| 364 |
-
>
|
| 365 |
-
Each axis is
|
| 366 |
-
normalized from 0–100
|
| 367 |
-
across all displayed
|
| 368 |
-
configurations.
|
| 369 |
-
Latency is inverted so
|
| 370 |
-
further outward always
|
| 371 |
-
means better.
|
| 372 |
</p>
|
| 373 |
</div>
|
| 374 |
);
|
|
|
|
| 9 |
Tooltip,
|
| 10 |
Legend,
|
| 11 |
} from "recharts";
|
| 12 |
+
import { parseConfigDetails } from "../../utils/configUtils";
|
| 13 |
|
| 14 |
const METRICS = [
|
| 15 |
{ key: "faithfulness", label: "Faithfulness" },
|
|
|
|
| 21 |
|
| 22 |
const LOWER_IS_BETTER = new Set(["latency"]);
|
| 23 |
|
| 24 |
+
const getColor = (index) => `hsl(${(index * 137.508) % 360}, 70%, 55%)`;
|
|
|
|
| 25 |
|
| 26 |
function buildNormalizers(rows) {
|
| 27 |
const map = {};
|
|
|
|
| 41 |
const range = max - min;
|
| 42 |
|
| 43 |
map[key] = (v) => {
|
| 44 |
+
if (v == null) return null;
|
| 45 |
+
const t = range === 0 ? 1 : (v - min) / range;
|
| 46 |
+
const goodness = LOWER_IS_BETTER.has(key) ? 1 - t : t;
|
| 47 |
+
return Math.round(goodness * 100);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
};
|
| 49 |
});
|
| 50 |
|
| 51 |
return map;
|
| 52 |
}
|
| 53 |
|
| 54 |
+
export default function RadarView({ data }) {
|
|
|
|
|
|
|
| 55 |
const rows = data ?? [];
|
| 56 |
|
| 57 |
const displayRows = useMemo(() => {
|
| 58 |
+
const sorted = [...rows].sort(
|
| 59 |
+
(a, b) => (a.averageRank ?? 999) - (b.averageRank ?? 999)
|
| 60 |
+
);
|
| 61 |
+
return sorted.slice(0, 8);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
}, [rows]);
|
| 63 |
|
| 64 |
+
const normalizers = useMemo(() => buildNormalizers(displayRows), [displayRows]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
const chartData = useMemo(() => {
|
| 67 |
+
return METRICS.map(({ key, label }) => {
|
| 68 |
+
const point = { metric: label };
|
| 69 |
+
displayRows.forEach((row) => {
|
| 70 |
+
const cLabel = row.configLabel || row.config;
|
| 71 |
+
point[cLabel] = normalizers[key](row[key]) ?? 0;
|
| 72 |
+
});
|
| 73 |
+
return point;
|
| 74 |
+
});
|
| 75 |
+
}, [displayRows, normalizers]);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
const rawMap = useMemo(() => {
|
| 78 |
const map = {};
|
|
|
|
| 79 |
displayRows.forEach((row) => {
|
| 80 |
+
const cLabel = row.configLabel || row.config;
|
| 81 |
+
map[cLabel] = {
|
| 82 |
+
details: row.configDetails || parseConfigDetails(row.config),
|
| 83 |
+
metrics: {},
|
| 84 |
+
};
|
| 85 |
+
METRICS.forEach(({ key }) => {
|
| 86 |
+
map[cLabel].metrics[key] = row[key];
|
| 87 |
+
});
|
| 88 |
});
|
|
|
|
| 89 |
return map;
|
| 90 |
}, [displayRows]);
|
| 91 |
|
| 92 |
+
const CustomTooltip = ({ active, payload, label }) => {
|
| 93 |
+
if (!active || !payload?.length) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
+
const metricKey = METRICS.find((m) => m.label === label)?.key;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
return (
|
| 98 |
<div
|
| 99 |
style={{
|
| 100 |
+
background: "rgba(15, 20, 36, 0.97)",
|
| 101 |
+
border: "1px solid rgba(168, 85, 247, 0.35)",
|
| 102 |
+
borderRadius: 14,
|
| 103 |
+
padding: "12px 16px",
|
|
|
|
|
|
|
|
|
|
| 104 |
fontSize: 12,
|
| 105 |
+
minWidth: 260,
|
| 106 |
+
boxShadow: "0 16px 36px rgba(0,0,0,0.6)",
|
| 107 |
}}
|
| 108 |
>
|
| 109 |
+
<div style={{ color: "white", fontWeight: 700, marginBottom: 10, fontSize: "13px", borderBottom: "1px solid rgba(255,255,255,0.08)", paddingBottom: "6px" }}>
|
| 110 |
+
📊 {label} Comparison
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
</div>
|
| 112 |
|
| 113 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
| 114 |
+
{payload.map((entry) => {
|
| 115 |
+
const info = rawMap[entry.dataKey];
|
| 116 |
+
const rawVal = info?.metrics?.[metricKey];
|
| 117 |
+
const details = info?.details;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
return (
|
| 120 |
<div
|
| 121 |
+
key={entry.dataKey}
|
| 122 |
style={{
|
| 123 |
+
padding: "6px 8px",
|
| 124 |
+
borderRadius: "8px",
|
| 125 |
+
background: "rgba(255,255,255,0.03)",
|
| 126 |
+
borderLeft: `3px solid ${entry.color}`,
|
| 127 |
}}
|
| 128 |
>
|
| 129 |
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 130 |
+
<span style={{ color: entry.color, fontWeight: 700 }}>{entry.dataKey}</span>
|
| 131 |
+
<span style={{ color: "#ffffff", fontWeight: 600 }}>
|
| 132 |
+
Score: {entry.value}/100 {rawVal != null ? `(${Number(rawVal).toFixed(2)})` : ""}
|
| 133 |
+
</span>
|
| 134 |
+
</div>
|
| 135 |
+
{details && (
|
| 136 |
+
<div style={{ fontSize: "10.5px", color: "rgba(255,255,255,0.6)", marginTop: "2px" }}>
|
| 137 |
+
{details.model} · {details.retrieval} · {details.reranker}
|
| 138 |
+
</div>
|
| 139 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
</div>
|
| 141 |
+
);
|
| 142 |
+
})}
|
| 143 |
+
</div>
|
| 144 |
</div>
|
| 145 |
);
|
| 146 |
};
|
| 147 |
|
| 148 |
+
if (displayRows.length === 0) {
|
| 149 |
return (
|
| 150 |
+
<p style={{ margin: 0, fontSize: 13, color: "rgba(255,255,255,0.35)" }}>
|
| 151 |
+
No configurations to plot yet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
</p>
|
| 153 |
);
|
| 154 |
}
|
| 155 |
|
| 156 |
return (
|
| 157 |
<div>
|
| 158 |
+
<ResponsiveContainer width="100%" height={380}>
|
| 159 |
+
<RadarChart data={chartData} margin={{ top: 20, right: 30, bottom: 20, left: 30 }}>
|
| 160 |
+
<PolarGrid stroke="rgba(255,255,255,0.08)" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
<PolarAngleAxis
|
| 162 |
dataKey="metric"
|
| 163 |
+
tick={{ fill: "rgba(255,255,255,0.85)", fontSize: 12, fontWeight: 600 }}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
/>
|
|
|
|
| 165 |
<PolarRadiusAxis
|
| 166 |
+
angle={30}
|
| 167 |
domain={[0, 100]}
|
| 168 |
+
stroke="rgba(255,255,255,0.2)"
|
| 169 |
+
tick={{ fill: "rgba(255,255,255,0.4)", fontSize: 10 }}
|
|
|
|
|
|
|
|
|
|
| 170 |
/>
|
| 171 |
|
| 172 |
+
<Tooltip content={<CustomTooltip />} />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
| 174 |
<Legend
|
| 175 |
layout="horizontal"
|
|
|
|
| 178 |
fontSize: 12,
|
| 179 |
maxHeight: 70,
|
| 180 |
overflowY: "auto",
|
| 181 |
+
paddingTop: "10px",
|
| 182 |
}}
|
| 183 |
+
formatter={(value) => (
|
| 184 |
+
<span style={{ color: "rgba(255,255,255,0.85)", fontWeight: 600, marginRight: 8 }}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
{value}
|
| 186 |
</span>
|
| 187 |
)}
|
| 188 |
/>
|
| 189 |
|
| 190 |
+
{displayRows.map((row, i) => {
|
| 191 |
+
const cLabel = row.configLabel || row.config;
|
| 192 |
+
return (
|
| 193 |
<Radar
|
| 194 |
+
key={cLabel}
|
| 195 |
+
name={cLabel}
|
| 196 |
+
dataKey={cLabel}
|
| 197 |
+
stroke={getColor(i)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
fill={getColor(i)}
|
| 199 |
+
fillOpacity={0.12}
|
|
|
|
|
|
|
| 200 |
strokeWidth={2}
|
| 201 |
/>
|
| 202 |
+
);
|
| 203 |
+
})}
|
| 204 |
</RadarChart>
|
| 205 |
</ResponsiveContainer>
|
| 206 |
|
| 207 |
+
<p style={{ margin: "16px 0 0", fontSize: 11, color: "rgba(255,255,255,0.3)" }}>
|
| 208 |
+
Each axis is normalized from 0–100 across displayed configurations. Latency is inverted so outward represents better speed.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
</p>
|
| 210 |
</div>
|
| 211 |
);
|
frontend/src/gaugepilot/components/visualizations/RankingBarView.jsx
CHANGED
|
@@ -2,19 +2,23 @@ import { useMemo } from "react";
|
|
| 2 |
import {
|
| 3 |
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell,
|
| 4 |
} from "recharts";
|
|
|
|
| 5 |
|
| 6 |
export default function RankingBarView({ data }) {
|
| 7 |
const rows = data ?? [];
|
| 8 |
|
| 9 |
const accent = "#4f6ef7";
|
| 10 |
-
const green = "#22c55e";
|
| 11 |
|
| 12 |
const ranked = useMemo(
|
| 13 |
() =>
|
| 14 |
rows
|
| 15 |
.filter((r) => r.averageRank != null)
|
| 16 |
-
.map((r) => ({
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
.sort((a, b) => a.averageRank - b.averageRank),
|
| 19 |
[rows]
|
| 20 |
);
|
|
@@ -35,16 +39,28 @@ export default function RankingBarView({ data }) {
|
|
| 35 |
const CustomTooltip = ({ active, payload }) => {
|
| 36 |
if (!active || !payload?.length) return null;
|
| 37 |
const p = payload[0].payload;
|
|
|
|
|
|
|
| 38 |
return (
|
| 39 |
<div style={{
|
| 40 |
-
background: "rgba(15,
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
}}>
|
| 43 |
-
<div style={{ fontWeight: 700, color: "
|
| 44 |
-
{p.
|
|
|
|
| 45 |
</div>
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
</div>
|
| 49 |
</div>
|
| 50 |
);
|
|
@@ -64,8 +80,8 @@ export default function RankingBarView({ data }) {
|
|
| 64 |
<BarChart data={ranked} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
| 65 |
<CartesianGrid stroke="rgba(255,255,255,0.06)" vertical={false} />
|
| 66 |
<XAxis
|
| 67 |
-
dataKey="
|
| 68 |
-
stroke="rgba(255,255,255,0.35)" tick={{ fill: "rgba(255,255,255,0.
|
| 69 |
interval={0} angle={-20} textAnchor="end" height={60}
|
| 70 |
/>
|
| 71 |
<YAxis
|
|
@@ -79,7 +95,7 @@ export default function RankingBarView({ data }) {
|
|
| 79 |
</BarChart>
|
| 80 |
</ResponsiveContainer>
|
| 81 |
<p style={{ margin: "14px 0 0", fontSize: "11px", color: "rgba(255,255,255,0.3)" }}>
|
| 82 |
-
Sorted best to worst; greener bars indicate a stronger average rank across all metrics.
|
| 83 |
</p>
|
| 84 |
</div>
|
| 85 |
);
|
|
|
|
| 2 |
import {
|
| 3 |
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell,
|
| 4 |
} from "recharts";
|
| 5 |
+
import { parseConfigDetails } from "../../utils/configUtils";
|
| 6 |
|
| 7 |
export default function RankingBarView({ data }) {
|
| 8 |
const rows = data ?? [];
|
| 9 |
|
| 10 |
const accent = "#4f6ef7";
|
|
|
|
| 11 |
|
| 12 |
const ranked = useMemo(
|
| 13 |
() =>
|
| 14 |
rows
|
| 15 |
.filter((r) => r.averageRank != null)
|
| 16 |
+
.map((r) => ({
|
| 17 |
+
config: r.config,
|
| 18 |
+
configLabel: r.configLabel || r.config,
|
| 19 |
+
configDetails: r.configDetails || parseConfigDetails(r.config),
|
| 20 |
+
averageRank: r.averageRank,
|
| 21 |
+
}))
|
| 22 |
.sort((a, b) => a.averageRank - b.averageRank),
|
| 23 |
[rows]
|
| 24 |
);
|
|
|
|
| 39 |
const CustomTooltip = ({ active, payload }) => {
|
| 40 |
if (!active || !payload?.length) return null;
|
| 41 |
const p = payload[0].payload;
|
| 42 |
+
const details = p.configDetails || parseConfigDetails(p.config);
|
| 43 |
+
|
| 44 |
return (
|
| 45 |
<div style={{
|
| 46 |
+
background: "rgba(15, 20, 36, 0.97)",
|
| 47 |
+
border: "1px solid rgba(168, 85, 247, 0.35)",
|
| 48 |
+
borderRadius: "14px",
|
| 49 |
+
padding: "12px 16px",
|
| 50 |
+
fontSize: "12px",
|
| 51 |
+
boxShadow: "0 16px 36px rgba(0,0,0,0.6)",
|
| 52 |
+
maxWidth: "300px",
|
| 53 |
}}>
|
| 54 |
+
<div style={{ fontWeight: 700, color: "#ffffff", fontSize: "13px", marginBottom: "6px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 55 |
+
<span>🏷️ {p.configLabel}</span>
|
| 56 |
+
<span style={{ color: "#38bdf8", fontSize: "11px" }}>Rank: {Number(p.averageRank).toFixed(2)}</span>
|
| 57 |
</div>
|
| 58 |
+
|
| 59 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "3px", fontSize: "11px", color: "rgba(255,255,255,0.75)", borderTop: "1px solid rgba(255,255,255,0.08)", paddingTop: "6px" }}>
|
| 60 |
+
<div><strong style={{ color: "rgba(255,255,255,0.5)" }}>Model:</strong> {details.model}</div>
|
| 61 |
+
<div><strong style={{ color: "rgba(255,255,255,0.5)" }}>Retrieval:</strong> {details.retrieval}</div>
|
| 62 |
+
<div><strong style={{ color: "rgba(255,255,255,0.5)" }}>Reranker:</strong> {details.reranker}</div>
|
| 63 |
+
<div><strong style={{ color: "rgba(255,255,255,0.5)" }}>Enhancements:</strong> {details.enhancements?.join(", ")}</div>
|
| 64 |
</div>
|
| 65 |
</div>
|
| 66 |
);
|
|
|
|
| 80 |
<BarChart data={ranked} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
| 81 |
<CartesianGrid stroke="rgba(255,255,255,0.06)" vertical={false} />
|
| 82 |
<XAxis
|
| 83 |
+
dataKey="configLabel"
|
| 84 |
+
stroke="rgba(255,255,255,0.35)" tick={{ fill: "rgba(255,255,255,0.75)", fontSize: 11, fontWeight: 600 }}
|
| 85 |
interval={0} angle={-20} textAnchor="end" height={60}
|
| 86 |
/>
|
| 87 |
<YAxis
|
|
|
|
| 95 |
</BarChart>
|
| 96 |
</ResponsiveContainer>
|
| 97 |
<p style={{ margin: "14px 0 0", fontSize: "11px", color: "rgba(255,255,255,0.3)" }}>
|
| 98 |
+
Sorted best to worst; greener bars indicate a stronger average rank across all metrics. Hover to inspect configuration parameters.
|
| 99 |
</p>
|
| 100 |
</div>
|
| 101 |
);
|
frontend/src/gaugepilot/components/visualizations/ScatterView.jsx
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
| 3 |
ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell,
|
| 4 |
} from "recharts";
|
| 5 |
import ExperimentSelector from "../ExperimentSelector";
|
|
|
|
| 6 |
|
| 7 |
const METRICS = [
|
| 8 |
{ value: "faithfulness", label: "Faithfulness", description: "Answer accuracy" },
|
|
@@ -24,26 +25,44 @@ export default function ScatterView({ data }) {
|
|
| 24 |
() =>
|
| 25 |
(data ?? [])
|
| 26 |
.filter((row) => row[xMetric] != null && row[yMetric] != null)
|
| 27 |
-
.map((row) => ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
[data, xMetric, yMetric]
|
| 29 |
);
|
| 30 |
|
| 31 |
const CustomTooltip = ({ active, payload }) => {
|
| 32 |
if (!active || !payload?.length) return null;
|
| 33 |
const p = payload[0].payload;
|
|
|
|
|
|
|
| 34 |
return (
|
| 35 |
<div style={{
|
| 36 |
-
background: "rgba(15,
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}}>
|
| 39 |
-
<div style={{ fontWeight: 700, color: "white", marginBottom: "
|
| 40 |
-
{p.
|
| 41 |
</div>
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
| 44 |
</div>
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
| 47 |
</div>
|
| 48 |
</div>
|
| 49 |
);
|
|
|
|
| 3 |
ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell,
|
| 4 |
} from "recharts";
|
| 5 |
import ExperimentSelector from "../ExperimentSelector";
|
| 6 |
+
import { parseConfigDetails } from "../../utils/configUtils";
|
| 7 |
|
| 8 |
const METRICS = [
|
| 9 |
{ value: "faithfulness", label: "Faithfulness", description: "Answer accuracy" },
|
|
|
|
| 25 |
() =>
|
| 26 |
(data ?? [])
|
| 27 |
.filter((row) => row[xMetric] != null && row[yMetric] != null)
|
| 28 |
+
.map((row) => ({
|
| 29 |
+
config: row.config,
|
| 30 |
+
configLabel: row.configLabel || row.config,
|
| 31 |
+
configDetails: row.configDetails || parseConfigDetails(row.config),
|
| 32 |
+
x: row[xMetric],
|
| 33 |
+
y: row[yMetric],
|
| 34 |
+
})),
|
| 35 |
[data, xMetric, yMetric]
|
| 36 |
);
|
| 37 |
|
| 38 |
const CustomTooltip = ({ active, payload }) => {
|
| 39 |
if (!active || !payload?.length) return null;
|
| 40 |
const p = payload[0].payload;
|
| 41 |
+
const details = p.configDetails || parseConfigDetails(p.config);
|
| 42 |
+
|
| 43 |
return (
|
| 44 |
<div style={{
|
| 45 |
+
background: "rgba(15, 20, 36, 0.97)",
|
| 46 |
+
border: "1px solid rgba(168, 85, 247, 0.35)",
|
| 47 |
+
borderRadius: "14px",
|
| 48 |
+
padding: "12px 16px",
|
| 49 |
+
fontSize: "12px",
|
| 50 |
+
boxShadow: "0 16px 36px rgba(0,0,0,0.6)",
|
| 51 |
+
maxWidth: "300px",
|
| 52 |
}}>
|
| 53 |
+
<div style={{ fontWeight: 700, color: "white", fontSize: "13px", marginBottom: "6px" }}>
|
| 54 |
+
🏷️ {p.configLabel}
|
| 55 |
</div>
|
| 56 |
+
|
| 57 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "4px", fontSize: "11.5px", color: "rgba(255,255,255,0.8)" }}>
|
| 58 |
+
<div>{metricLabel(xMetric)}: <strong style={{ color: "#38bdf8" }}>{Number(p.x).toFixed(2)}</strong></div>
|
| 59 |
+
<div>{metricLabel(yMetric)}: <strong style={{ color: "#c084fc" }}>{Number(p.y).toFixed(2)}</strong></div>
|
| 60 |
</div>
|
| 61 |
+
|
| 62 |
+
<div style={{ display: "flex", flexDirection: "column", gap: "2px", fontSize: "10.5px", color: "rgba(255,255,255,0.6)", borderTop: "1px solid rgba(255,255,255,0.08)", paddingTop: "6px", marginTop: "6px" }}>
|
| 63 |
+
<div>Model: {details.model}</div>
|
| 64 |
+
<div>Retrieval: {details.retrieval} · {details.reranker}</div>
|
| 65 |
+
<div>Enhancements: {details.enhancements?.join(", ")}</div>
|
| 66 |
</div>
|
| 67 |
</div>
|
| 68 |
);
|
frontend/src/gaugepilot/hooks/useVisualizations.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
//
|
| 4 |
-
//
|
| 5 |
-
//
|
| 6 |
-
// { config, faithfulness, grounding, quality, coverage, latency, averageRank }
|
| 7 |
-
//
|
| 8 |
-
// This is the shape every visualization component consumes, so charts never
|
| 9 |
-
// need to know about the underlying leaderboard structure.
|
| 10 |
-
//
|
| 11 |
-
// Defensive by design: if a row already carries a nested `metrics` object
|
| 12 |
-
// (e.g. { config_name, metrics: { faithfulness, semantic_grounding, ... } }),
|
| 13 |
-
// that takes priority over the per-category `value` field, so this keeps
|
| 14 |
-
// working if the API response shape evolves either way.
|
| 15 |
|
| 16 |
const CATEGORY_TO_METRIC = {
|
| 17 |
faithfulness: "faithfulness",
|
|
@@ -44,14 +34,21 @@ function extractValue(row, category) {
|
|
| 44 |
export function useVisualizations(leaderboard) {
|
| 45 |
if (!leaderboard) return [];
|
| 46 |
|
|
|
|
| 47 |
const configMap = new Map();
|
|
|
|
| 48 |
const ensureConfig = (name) => {
|
| 49 |
-
if (!configMap.has(name))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
return configMap.get(name);
|
| 51 |
};
|
| 52 |
|
| 53 |
-
// Seed every config that appears anywhere in the leaderboard first
|
| 54 |
-
// configuration missing one metric still shows up with the rest filled in.
|
| 55 |
Object.values(leaderboard).forEach((rows) => {
|
| 56 |
(rows ?? []).forEach((row) => {
|
| 57 |
if (row?.config_name) ensureConfig(row.config_name);
|
|
@@ -66,9 +63,6 @@ export function useVisualizations(leaderboard) {
|
|
| 66 |
});
|
| 67 |
});
|
| 68 |
|
| 69 |
-
// Average rank lives on the "overall" array rather than as its own
|
| 70 |
-
// category — the backend has used both `average_rank` and `avg_rank`
|
| 71 |
-
// for this field at different times, so support both.
|
| 72 |
(leaderboard.overall ?? []).forEach((row) => {
|
| 73 |
if (!row?.config_name) return;
|
| 74 |
const target = ensureConfig(row.config_name);
|
|
|
|
| 1 |
+
import { buildConfigLabelMap, parseConfigDetails } from "../utils/configUtils";
|
| 2 |
+
|
| 3 |
+
// Normalizes the leaderboard payload into one flat row per configuration:
|
| 4 |
+
// { config, configLabel, configDetails, faithfulness, grounding, quality, coverage, latency, averageRank }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
const CATEGORY_TO_METRIC = {
|
| 7 |
faithfulness: "faithfulness",
|
|
|
|
| 34 |
export function useVisualizations(leaderboard) {
|
| 35 |
if (!leaderboard) return [];
|
| 36 |
|
| 37 |
+
const labelMap = buildConfigLabelMap(leaderboard);
|
| 38 |
const configMap = new Map();
|
| 39 |
+
|
| 40 |
const ensureConfig = (name) => {
|
| 41 |
+
if (!configMap.has(name)) {
|
| 42 |
+
configMap.set(name, {
|
| 43 |
+
config: name,
|
| 44 |
+
configLabel: labelMap.get(name) || `Config ${configMap.size + 1}`,
|
| 45 |
+
configDetails: parseConfigDetails(name),
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
return configMap.get(name);
|
| 49 |
};
|
| 50 |
|
| 51 |
+
// Seed every config that appears anywhere in the leaderboard first
|
|
|
|
| 52 |
Object.values(leaderboard).forEach((rows) => {
|
| 53 |
(rows ?? []).forEach((row) => {
|
| 54 |
if (row?.config_name) ensureConfig(row.config_name);
|
|
|
|
| 63 |
});
|
| 64 |
});
|
| 65 |
|
|
|
|
|
|
|
|
|
|
| 66 |
(leaderboard.overall ?? []).forEach((row) => {
|
| 67 |
if (!row?.config_name) return;
|
| 68 |
const target = ensureConfig(row.config_name);
|
frontend/src/gaugepilot/pages/Aianalysis.jsx
CHANGED
|
@@ -521,7 +521,7 @@ export default function AIAnalysis({ selectedRun, onRunRefresh }) {
|
|
| 521 |
)}
|
| 522 |
|
| 523 |
{/* Report cards */}
|
| 524 |
-
<div style={{ display: "grid", gridTemplateColumns: "
|
| 525 |
<InsightCard report={insightReport} />
|
| 526 |
<RecommendationCard report={recommendationReport} />
|
| 527 |
</div>
|
|
|
|
| 521 |
)}
|
| 522 |
|
| 523 |
{/* Report cards */}
|
| 524 |
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 420px), 1fr))", gap: "16px" }}>
|
| 525 |
<InsightCard report={insightReport} />
|
| 526 |
<RecommendationCard report={recommendationReport} />
|
| 527 |
</div>
|
frontend/src/gaugepilot/pages/ExperimentSetup.jsx
CHANGED
|
@@ -384,7 +384,7 @@ export default function ExperimentSetup({ onRunChange }) {
|
|
| 384 |
style={{
|
| 385 |
maxWidth: "1280px",
|
| 386 |
margin: "0 auto",
|
| 387 |
-
padding: "32px 28px 80px",
|
| 388 |
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
| 389 |
}}
|
| 390 |
>
|
|
@@ -772,7 +772,8 @@ export default function ExperimentSetup({ onRunChange }) {
|
|
| 772 |
position: "absolute",
|
| 773 |
top: "calc(100% + 8px)",
|
| 774 |
right: 0,
|
| 775 |
-
width: "
|
|
|
|
| 776 |
maxHeight: "440px",
|
| 777 |
overflowY: "auto",
|
| 778 |
zIndex: 9999,
|
|
@@ -890,7 +891,7 @@ export default function ExperimentSetup({ onRunChange }) {
|
|
| 890 |
<div
|
| 891 |
style={{
|
| 892 |
display: "grid",
|
| 893 |
-
gridTemplateColumns: "repeat(auto-fit, minmax(
|
| 894 |
gap: "20px",
|
| 895 |
alignItems: "stretch",
|
| 896 |
marginBottom: "24px",
|
|
|
|
| 384 |
style={{
|
| 385 |
maxWidth: "1280px",
|
| 386 |
margin: "0 auto",
|
| 387 |
+
padding: "clamp(16px, 3vw, 32px) clamp(14px, 3vw, 28px) 80px",
|
| 388 |
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
| 389 |
}}
|
| 390 |
>
|
|
|
|
| 772 |
position: "absolute",
|
| 773 |
top: "calc(100% + 8px)",
|
| 774 |
right: 0,
|
| 775 |
+
width: "min(340px, calc(100vw - 32px))",
|
| 776 |
+
maxWidth: "calc(100vw - 32px)",
|
| 777 |
maxHeight: "440px",
|
| 778 |
overflowY: "auto",
|
| 779 |
zIndex: 9999,
|
|
|
|
| 891 |
<div
|
| 892 |
style={{
|
| 893 |
display: "grid",
|
| 894 |
+
gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 340px), 1fr))",
|
| 895 |
gap: "20px",
|
| 896 |
alignItems: "stretch",
|
| 897 |
marginBottom: "24px",
|
frontend/src/gaugepilot/pages/Leaderboards.jsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
-
import { useState } from "react";
|
| 2 |
import OverallTable from "../components/leaderboards/OverallTable";
|
| 3 |
import ExperimentSelector from "../components/ExperimentSelector";
|
|
|
|
|
|
|
| 4 |
|
| 5 |
const METRIC_META = {
|
| 6 |
overall: { label: "Overall Rankings", desc: "Combined benchmark ranking across all evaluation metrics.", icon: "🏆" },
|
|
@@ -35,6 +37,7 @@ export default function Leaderboards({ leaderboard }) {
|
|
| 35 |
color, fontSize: "11px", fontWeight: 600, letterSpacing: "0.02em",
|
| 36 |
});
|
| 37 |
|
|
|
|
| 38 |
|
| 39 |
// ── Derived KPI values ─────────────────────────────────────────────────────
|
| 40 |
const overall = leaderboard?.overall ?? [];
|
|
@@ -45,10 +48,6 @@ export default function Leaderboards({ leaderboard }) {
|
|
| 45 |
const fastestConfig = [...latency].sort((a, b) => (a.value ?? 0) - (b.value ?? 0))[0]?.config_name ?? null;
|
| 46 |
const topFaithfulness = faithfulness[0]?.config_name ?? null;
|
| 47 |
|
| 48 |
-
const formatName = (name) =>
|
| 49 |
-
name?.replaceAll("_", " ").replaceAll("+", " + ")
|
| 50 |
-
.replace("NoRewrite", "(No Rewrite)").replace("NoReranker", "(No Reranker)") ?? "—";
|
| 51 |
-
|
| 52 |
// ── Empty state ────────────────────────────────────────────────────────────
|
| 53 |
if (!leaderboard || overall.length === 0) {
|
| 54 |
return (
|
|
@@ -119,14 +118,14 @@ export default function Leaderboards({ leaderboard }) {
|
|
| 119 |
</div>
|
| 120 |
|
| 121 |
{/* ── KPI Row ──────────────────────────────────────────────────────────── */}
|
| 122 |
-
<div style={{ display: "grid", gridTemplateColumns: "repeat(
|
| 123 |
{[
|
| 124 |
-
{ label: "Best Overall",
|
| 125 |
-
{ label: "Fastest Config",
|
| 126 |
-
{ label: "Top Faithfulness",
|
| 127 |
-
{ label: "Configs Evaluated",
|
| 128 |
-
].map((
|
| 129 |
-
<div key={label} style={{
|
| 130 |
...card, padding: "16px 20px",
|
| 131 |
display: "flex", alignItems: "center", gap: "14px",
|
| 132 |
transition: "transform 0.2s, box-shadow 0.2s",
|
|
@@ -142,15 +141,23 @@ export default function Leaderboards({ leaderboard }) {
|
|
| 142 |
>
|
| 143 |
<div style={{
|
| 144 |
width: 40, height: 40, borderRadius: "10px", flexShrink: 0,
|
| 145 |
-
background: `${color}18`, display: "flex", alignItems: "center",
|
| 146 |
justifyContent: "center", fontSize: "18px",
|
| 147 |
-
}}>{icon}</div>
|
| 148 |
-
<div style={{ minWidth: 0 }}>
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
</div>
|
| 155 |
</div>
|
| 156 |
))}
|
|
@@ -199,7 +206,7 @@ export default function Leaderboards({ leaderboard }) {
|
|
| 199 |
</div>
|
| 200 |
</div>
|
| 201 |
|
| 202 |
-
<OverallTable data={leaderboard[selectedMetric]} />
|
| 203 |
</div>
|
| 204 |
|
| 205 |
</div>
|
|
|
|
| 1 |
+
import { useState, useMemo } from "react";
|
| 2 |
import OverallTable from "../components/leaderboards/OverallTable";
|
| 3 |
import ExperimentSelector from "../components/ExperimentSelector";
|
| 4 |
+
import ConfigBadge from "../components/ConfigBadge";
|
| 5 |
+
import { buildConfigLabelMap } from "../utils/configUtils";
|
| 6 |
|
| 7 |
const METRIC_META = {
|
| 8 |
overall: { label: "Overall Rankings", desc: "Combined benchmark ranking across all evaluation metrics.", icon: "🏆" },
|
|
|
|
| 37 |
color, fontSize: "11px", fontWeight: 600, letterSpacing: "0.02em",
|
| 38 |
});
|
| 39 |
|
| 40 |
+
const labelMap = useMemo(() => buildConfigLabelMap(leaderboard), [leaderboard]);
|
| 41 |
|
| 42 |
// ── Derived KPI values ─────────────────────────────────────────────────────
|
| 43 |
const overall = leaderboard?.overall ?? [];
|
|
|
|
| 48 |
const fastestConfig = [...latency].sort((a, b) => (a.value ?? 0) - (b.value ?? 0))[0]?.config_name ?? null;
|
| 49 |
const topFaithfulness = faithfulness[0]?.config_name ?? null;
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
// ── Empty state ────────────────────────────────────────────────────────────
|
| 52 |
if (!leaderboard || overall.length === 0) {
|
| 53 |
return (
|
|
|
|
| 118 |
</div>
|
| 119 |
|
| 120 |
{/* ── KPI Row ──────────────────────────────────────────────────────────── */}
|
| 121 |
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 220px), 1fr))", gap: "12px", marginBottom: "16px" }}>
|
| 122 |
{[
|
| 123 |
+
{ label: "Best Overall", configName: bestConfig, icon: "🥇", color: "#f59e0b" },
|
| 124 |
+
{ label: "Fastest Config", configName: fastestConfig, icon: "⚡", color: "#22c55e" },
|
| 125 |
+
{ label: "Top Faithfulness", configName: topFaithfulness, icon: "📋", color: accent },
|
| 126 |
+
{ label: "Configs Evaluated", count: totalConfigs, icon: "🔬", color: "#a78bfa" },
|
| 127 |
+
].map((item) => (
|
| 128 |
+
<div key={item.label} style={{
|
| 129 |
...card, padding: "16px 20px",
|
| 130 |
display: "flex", alignItems: "center", gap: "14px",
|
| 131 |
transition: "transform 0.2s, box-shadow 0.2s",
|
|
|
|
| 141 |
>
|
| 142 |
<div style={{
|
| 143 |
width: 40, height: 40, borderRadius: "10px", flexShrink: 0,
|
| 144 |
+
background: `${item.color}18`, display: "flex", alignItems: "center",
|
| 145 |
justifyContent: "center", fontSize: "18px",
|
| 146 |
+
}}>{item.icon}</div>
|
| 147 |
+
<div style={{ minWidth: 0, flex: 1 }}>
|
| 148 |
+
{item.configName ? (
|
| 149 |
+
<div style={{ display: "flex", alignItems: "center" }}>
|
| 150 |
+
<ConfigBadge
|
| 151 |
+
configName={item.configName}
|
| 152 |
+
label={labelMap.get(item.configName) || "Config"}
|
| 153 |
+
/>
|
| 154 |
+
</div>
|
| 155 |
+
) : (
|
| 156 |
+
<div style={{
|
| 157 |
+
fontSize: "14px", fontWeight: 700, color: "white", lineHeight: 1.2,
|
| 158 |
+
}}>{item.count ?? "—"}</div>
|
| 159 |
+
)}
|
| 160 |
+
<div style={{ fontSize: "11px", color: "rgba(255,255,255,0.35)", marginTop: "4px" }}>{item.label}</div>
|
| 161 |
</div>
|
| 162 |
</div>
|
| 163 |
))}
|
|
|
|
| 206 |
</div>
|
| 207 |
</div>
|
| 208 |
|
| 209 |
+
<OverallTable data={leaderboard[selectedMetric]} labelMap={labelMap} />
|
| 210 |
</div>
|
| 211 |
|
| 212 |
</div>
|
frontend/src/gaugepilot/utils/configUtils.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ─── GAUGEPILOT CONFIGURATION PARSER & LABELING UTILITIES ─────────────────────
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Parses raw configuration names (e.g., "gpt-oss-120b vector none Default",
|
| 5 |
+
* "gpt-oss-120b_hybrid_minilm_parent_child_all-mpnet-base-v2_Multi_Query_Query_Rewrite",
|
| 6 |
+
* "Hybrid+MiniLM", "BM25", "Vector_Only") into a structured architectural breakdown.
|
| 7 |
+
*/
|
| 8 |
+
export function parseConfigDetails(configName) {
|
| 9 |
+
if (!configName || typeof configName !== "string") {
|
| 10 |
+
return {
|
| 11 |
+
model: "Standard LLM",
|
| 12 |
+
retrieval: "Hybrid (Dense + BM25)",
|
| 13 |
+
reranker: "None",
|
| 14 |
+
chunker: "Parent-Child (1200/300)",
|
| 15 |
+
embedding: "all-mpnet-base-v2",
|
| 16 |
+
enhancements: ["Default (Baseline)"],
|
| 17 |
+
rawName: configName || "—",
|
| 18 |
+
};
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const raw = configName;
|
| 22 |
+
const lower = configName.toLowerCase().replaceAll("_", " ");
|
| 23 |
+
|
| 24 |
+
// 1. Detect Model
|
| 25 |
+
let model = "GPT-OSS 120B";
|
| 26 |
+
if (lower.includes("llama-3.3-70b") || lower.includes("llama 3.3 70b") || lower.includes("llama3")) {
|
| 27 |
+
model = "Llama 3.3 70B";
|
| 28 |
+
} else if (lower.includes("gpt-oss-120b") || lower.includes("gpt oss 120b") || lower.includes("gptoss")) {
|
| 29 |
+
model = "GPT-OSS 120B";
|
| 30 |
+
} else if (lower.includes("gemini-1.5-flash") || lower.includes("gemini 1.5 flash") || lower.includes("gemini")) {
|
| 31 |
+
model = "Gemini 1.5 Flash";
|
| 32 |
+
} else if (lower.includes("mixtral")) {
|
| 33 |
+
model = "Mixtral 8x7B";
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// 2. Detect Retrieval Method
|
| 37 |
+
let retrieval = "Hybrid (Dense + BM25)";
|
| 38 |
+
if (lower.includes("vector only") || (lower.includes("vector") && !lower.includes("hybrid"))) {
|
| 39 |
+
retrieval = "Vector (Dense FAISS)";
|
| 40 |
+
} else if (lower.includes("lexical") || lower.includes("bm25") && !lower.includes("hybrid")) {
|
| 41 |
+
retrieval = "Lexical (BM25 Keyword)";
|
| 42 |
+
} else if (lower.includes("hybrid")) {
|
| 43 |
+
retrieval = "Hybrid (Dense + BM25)";
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
// 3. Detect Reranker
|
| 47 |
+
let reranker = "None (Direct First-Stage)";
|
| 48 |
+
if (lower.includes("minilm")) {
|
| 49 |
+
reranker = "MiniLM Cross-Encoder";
|
| 50 |
+
} else if (lower.includes("tinybert")) {
|
| 51 |
+
reranker = "TinyBERT (Low-Latency)";
|
| 52 |
+
} else if (lower.includes("bge large") || lower.includes("bge-large")) {
|
| 53 |
+
reranker = "BGE Large Cross-Encoder";
|
| 54 |
+
} else if (lower.includes("bge m3") || lower.includes("bge-m3")) {
|
| 55 |
+
reranker = "BGE M3 Reranker";
|
| 56 |
+
} else if (lower.includes("noreranker") || lower.includes("none")) {
|
| 57 |
+
reranker = "None (Direct First-Stage)";
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
// 4. Detect Chunker
|
| 61 |
+
let chunker = "Parent-Child (1200/300)";
|
| 62 |
+
if (lower.includes("recursive")) {
|
| 63 |
+
chunker = "Recursive Splitter";
|
| 64 |
+
} else if (lower.includes("token")) {
|
| 65 |
+
chunker = "Fixed Token (512)";
|
| 66 |
+
} else if (lower.includes("parent child") || lower.includes("parent_child")) {
|
| 67 |
+
chunker = "Parent-Child (1200/300)";
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// 5. Detect Embedding Model
|
| 71 |
+
let embedding = "all-mpnet-base-v2";
|
| 72 |
+
if (lower.includes("bge-large-en") || lower.includes("bge large en")) {
|
| 73 |
+
embedding = "bge-large-en-v1.5";
|
| 74 |
+
} else if (lower.includes("text-embedding-3-small")) {
|
| 75 |
+
embedding = "text-embedding-3-small";
|
| 76 |
+
} else if (lower.includes("all-mpnet-base-v2") || lower.includes("mpnet")) {
|
| 77 |
+
embedding = "all-mpnet-base-v2";
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// 6. Detect Enhancements
|
| 81 |
+
const enhancements = [];
|
| 82 |
+
if (lower.includes("query rewrite") || lower.includes("query_rewrite")) enhancements.push("Query Rewrite");
|
| 83 |
+
if (lower.includes("multi query") || lower.includes("multi_query")) enhancements.push("Multi-Query");
|
| 84 |
+
if (lower.includes("hyde")) enhancements.push("HyDE");
|
| 85 |
+
if (lower.includes("step back") || lower.includes("step_back")) enhancements.push("Step-Back Prompting");
|
| 86 |
+
if (lower.includes("query expansion") || lower.includes("query_expansion")) enhancements.push("Query Expansion");
|
| 87 |
+
if (lower.includes("sub query") || lower.includes("sub_query")) enhancements.push("Sub-Query Decomposition");
|
| 88 |
+
if (lower.includes("metadata")) enhancements.push("Metadata Filtering");
|
| 89 |
+
if (lower.includes("routing")) enhancements.push("Intent Routing");
|
| 90 |
+
if (lower.includes("graph")) enhancements.push("GraphRAG");
|
| 91 |
+
if (lower.includes("compression")) enhancements.push("Context Compression");
|
| 92 |
+
|
| 93 |
+
if (enhancements.length === 0) {
|
| 94 |
+
enhancements.push("Default (Baseline Pipeline)");
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
return {
|
| 98 |
+
model,
|
| 99 |
+
retrieval,
|
| 100 |
+
reranker,
|
| 101 |
+
chunker,
|
| 102 |
+
embedding,
|
| 103 |
+
enhancements,
|
| 104 |
+
rawName: raw,
|
| 105 |
+
};
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/**
|
| 109 |
+
* Builds a deterministic mapping from raw config name -> "Config 1", "Config 2", etc.
|
| 110 |
+
* Orders configs by overall leaderboard rank when available, or by appearance.
|
| 111 |
+
*/
|
| 112 |
+
export function buildConfigLabelMap(leaderboardOrConfigs) {
|
| 113 |
+
const map = new Map();
|
| 114 |
+
let index = 1;
|
| 115 |
+
|
| 116 |
+
if (!leaderboardOrConfigs) return map;
|
| 117 |
+
|
| 118 |
+
// Case 1: Leaderboard object with overall array
|
| 119 |
+
if (leaderboardOrConfigs.overall && Array.isArray(leaderboardOrConfigs.overall)) {
|
| 120 |
+
leaderboardOrConfigs.overall.forEach((row) => {
|
| 121 |
+
const name = row?.config_name || row?.config;
|
| 122 |
+
if (name && !map.has(name)) {
|
| 123 |
+
map.set(name, `Config ${index++}`);
|
| 124 |
+
}
|
| 125 |
+
});
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
// Case 2: Array of rows/configs or visualization items
|
| 129 |
+
if (Array.isArray(leaderboardOrConfigs)) {
|
| 130 |
+
leaderboardOrConfigs.forEach((item) => {
|
| 131 |
+
const name = typeof item === "string" ? item : (item?.config_name || item?.config);
|
| 132 |
+
if (name && !map.has(name)) {
|
| 133 |
+
map.set(name, `Config ${index++}`);
|
| 134 |
+
}
|
| 135 |
+
});
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
// Also check other categories in leaderboard object
|
| 139 |
+
if (typeof leaderboardOrConfigs === "object" && !Array.isArray(leaderboardOrConfigs)) {
|
| 140 |
+
Object.values(leaderboardOrConfigs).forEach((categoryRows) => {
|
| 141 |
+
if (Array.isArray(categoryRows)) {
|
| 142 |
+
categoryRows.forEach((row) => {
|
| 143 |
+
const name = row?.config_name || row?.config;
|
| 144 |
+
if (name && !map.has(name)) {
|
| 145 |
+
map.set(name, `Config ${index++}`);
|
| 146 |
+
}
|
| 147 |
+
});
|
| 148 |
+
}
|
| 149 |
+
});
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
return map;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/**
|
| 156 |
+
* Helper to resolve a config's display label ("Config 1", "Config 2", etc.).
|
| 157 |
+
*/
|
| 158 |
+
export function getConfigDisplayLabel(configName, labelMap = null, fallbackIndex = null) {
|
| 159 |
+
if (labelMap && labelMap.has(configName)) {
|
| 160 |
+
return labelMap.get(configName);
|
| 161 |
+
}
|
| 162 |
+
if (fallbackIndex != null) {
|
| 163 |
+
return `Config ${fallbackIndex + 1}`;
|
| 164 |
+
}
|
| 165 |
+
return configName || "Config";
|
| 166 |
+
}
|
frontend/src/main.jsx
CHANGED
|
@@ -42,16 +42,27 @@ style.textContent = `
|
|
| 42 |
}
|
| 43 |
|
| 44 |
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 45 |
-
html, body, #root {
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 50 |
::-webkit-scrollbar-track { background: transparent; }
|
| 51 |
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.12); border-radius: 9999px; }
|
| 52 |
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.22); }
|
| 53 |
|
| 54 |
-
button, input { font: inherit; color: inherit; }
|
| 55 |
@keyframes pilot-spin { to { transform: rotate(360deg); } }
|
| 56 |
|
| 57 |
.mobile-menu-button { display: none; }
|
|
@@ -63,18 +74,41 @@ style.textContent = `
|
|
| 63 |
word-break: break-word;
|
| 64 |
}
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
.pilot-home {
|
| 68 |
width: 100% !important;
|
| 69 |
min-height: 100dvh !important;
|
| 70 |
-
height:
|
| 71 |
overflow-y: auto !important;
|
|
|
|
|
|
|
| 72 |
}
|
| 73 |
|
| 74 |
.pilot-home-topbar {
|
| 75 |
-
padding:
|
| 76 |
align-items: flex-start !important;
|
| 77 |
-
gap:
|
| 78 |
flex-direction: column !important;
|
| 79 |
}
|
| 80 |
|
|
@@ -87,45 +121,48 @@ style.textContent = `
|
|
| 87 |
|
| 88 |
.pilot-home-center {
|
| 89 |
justify-content: flex-start !important;
|
| 90 |
-
padding:
|
| 91 |
-
gap:
|
| 92 |
}
|
| 93 |
|
| 94 |
.pilot-home-grid {
|
| 95 |
width: 100% !important;
|
| 96 |
flex-direction: column !important;
|
| 97 |
-
gap:
|
|
|
|
| 98 |
}
|
| 99 |
|
| 100 |
.pilot-product-card {
|
| 101 |
width: 100% !important;
|
|
|
|
| 102 |
padding: 20px !important;
|
| 103 |
-
border-radius:
|
| 104 |
}
|
| 105 |
|
| 106 |
.pilot-home-footer {
|
| 107 |
-
padding:
|
| 108 |
flex-direction: column !important;
|
| 109 |
-
gap:
|
|
|
|
| 110 |
}
|
| 111 |
|
| 112 |
.auth-shell {
|
| 113 |
width: 100% !important;
|
| 114 |
min-height: 100dvh !important;
|
| 115 |
-
|
| 116 |
-
padding: 18px !important;
|
| 117 |
overflow-y: auto !important;
|
| 118 |
align-items: center !important;
|
| 119 |
}
|
| 120 |
|
| 121 |
.auth-panel {
|
| 122 |
width: 100% !important;
|
| 123 |
-
max-width:
|
|
|
|
| 124 |
}
|
| 125 |
|
| 126 |
.auth-title {
|
| 127 |
-
font-size:
|
| 128 |
-
letter-spacing: -
|
| 129 |
}
|
| 130 |
|
| 131 |
.docpilot-root,
|
|
@@ -140,38 +177,40 @@ style.textContent = `
|
|
| 140 |
display: inline-flex !important;
|
| 141 |
align-items: center !important;
|
| 142 |
justify-content: center !important;
|
| 143 |
-
width:
|
| 144 |
-
height:
|
| 145 |
-
padding:
|
| 146 |
background: var(--surface) !important;
|
| 147 |
-
color: var(--text-
|
| 148 |
border: 1px solid var(--border) !important;
|
| 149 |
-
border-radius:
|
| 150 |
cursor: pointer !important;
|
| 151 |
flex-shrink: 0 !important;
|
|
|
|
| 152 |
}
|
| 153 |
|
| 154 |
.mobile-drawer-backdrop {
|
| 155 |
display: block !important;
|
| 156 |
position: fixed !important;
|
| 157 |
inset: 0 !important;
|
| 158 |
-
background: rgba(0, 0, 0, 0.
|
|
|
|
| 159 |
border: 0 !important;
|
| 160 |
padding: 0 !important;
|
| 161 |
-
z-index:
|
| 162 |
}
|
| 163 |
|
| 164 |
.docpilot-sidebar,
|
| 165 |
.trace-sidebar {
|
| 166 |
position: fixed !important;
|
| 167 |
inset: 0 auto 0 0 !important;
|
| 168 |
-
width: min(
|
| 169 |
max-width: 320px !important;
|
| 170 |
height: 100dvh !important;
|
| 171 |
-
z-index:
|
| 172 |
transform: translateX(-105%) !important;
|
| 173 |
-
transition: transform 0.
|
| 174 |
-
box-shadow: 24px 0 60px rgba(0, 0, 0, 0.
|
| 175 |
}
|
| 176 |
|
| 177 |
.docpilot-sidebar.is-open,
|
|
@@ -186,93 +225,76 @@ style.textContent = `
|
|
| 186 |
}
|
| 187 |
|
| 188 |
.docpilot-topbar {
|
| 189 |
-
padding: 10px
|
| 190 |
-
gap:
|
| 191 |
-
align-items:
|
| 192 |
-
flex-wrap:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
}
|
| 194 |
|
| 195 |
.docpilot-active-document {
|
| 196 |
min-width: 0 !important;
|
| 197 |
-
flex: 1 1
|
| 198 |
overflow: hidden !important;
|
| 199 |
text-overflow: ellipsis !important;
|
| 200 |
white-space: nowrap !important;
|
| 201 |
}
|
| 202 |
|
| 203 |
.docpilot-actions {
|
| 204 |
-
|
| 205 |
gap: 6px !important;
|
| 206 |
-
|
| 207 |
-
|
| 208 |
}
|
| 209 |
|
| 210 |
-
.docpilot-actions button
|
| 211 |
-
|
|
|
|
| 212 |
white-space: nowrap !important;
|
|
|
|
| 213 |
}
|
| 214 |
|
| 215 |
.docpilot-chat-area {
|
| 216 |
-
padding:
|
| 217 |
}
|
| 218 |
|
| 219 |
.docpilot-message-user,
|
| 220 |
.docpilot-message-assistant {
|
| 221 |
max-width: 100% !important;
|
| 222 |
-
font-size:
|
| 223 |
}
|
| 224 |
|
| 225 |
.docpilot-message-user {
|
| 226 |
-
padding:
|
| 227 |
}
|
| 228 |
|
| 229 |
.docpilot-input-bar {
|
| 230 |
-
padding:
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
.docpilot-input-row {
|
| 234 |
-
gap: 8px !important;
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
-
.docpilot-input-row input {
|
| 238 |
-
min-width: 0 !important;
|
| 239 |
-
padding: 13px 14px !important;
|
| 240 |
-
font-size: 14px !important;
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
.docpilot-input-row button {
|
| 244 |
-
padding: 13px 14px !important;
|
| 245 |
-
min-width: 78px !important;
|
| 246 |
}
|
| 247 |
|
| 248 |
.trace-header {
|
| 249 |
-
padding:
|
| 250 |
}
|
| 251 |
|
| 252 |
.trace-title {
|
| 253 |
-
font-size:
|
| 254 |
-
letter-spacing: -
|
| 255 |
}
|
| 256 |
|
| 257 |
.trace-body {
|
| 258 |
display: block !important;
|
| 259 |
-
overflow:
|
| 260 |
min-width: 0 !important;
|
| 261 |
}
|
| 262 |
|
| 263 |
.trace-detail-panel {
|
| 264 |
-
height:
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
.trace-empty-state {
|
| 269 |
-
text-align: left !important;
|
| 270 |
-
color: #444 !important;
|
| 271 |
-
}
|
| 272 |
-
|
| 273 |
-
.trace-mobile-list-button {
|
| 274 |
-
display: inline-flex !important;
|
| 275 |
-
margin-top: 12px !important;
|
| 276 |
}
|
| 277 |
|
| 278 |
.trace-detail-header {
|
|
@@ -312,123 +334,112 @@ style.textContent = `
|
|
| 312 |
}
|
| 313 |
}
|
| 314 |
|
| 315 |
-
@media (max-width:
|
| 316 |
.pilot-home-topbar h1 {
|
| 317 |
-
font-size:
|
| 318 |
}
|
| 319 |
|
| 320 |
.pilot-home-actions button,
|
| 321 |
-
.
|
| 322 |
-
|
|
|
|
|
|
|
|
|
|
| 323 |
}
|
| 324 |
|
| 325 |
.auth-title {
|
| 326 |
-
font-size:
|
| 327 |
-
}
|
| 328 |
-
|
| 329 |
-
.docpilot-input-row {
|
| 330 |
-
flex-direction: column !important;
|
| 331 |
-
}
|
| 332 |
-
|
| 333 |
-
.docpilot-input-row button {
|
| 334 |
-
width: 100% !important;
|
| 335 |
}
|
| 336 |
|
| 337 |
.trace-header {
|
| 338 |
-
padding:
|
| 339 |
}
|
| 340 |
|
| 341 |
.trace-title {
|
| 342 |
-
font-size:
|
| 343 |
}
|
| 344 |
-
}
|
| 345 |
-
display: flex;
|
| 346 |
-
width: 100%;
|
| 347 |
-
height: 100vh;
|
| 348 |
-
background: var(--bg-primary);
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
-
.gauge-sidebar {
|
| 352 |
-
width: 260px;
|
| 353 |
-
border-right: 1px solid var(--border);
|
| 354 |
-
padding: 24px;
|
| 355 |
-
background: var(--bg-secondary);
|
| 356 |
-
}
|
| 357 |
-
|
| 358 |
-
.gauge-main {
|
| 359 |
-
flex: 1;
|
| 360 |
-
overflow-y: auto;
|
| 361 |
-
padding: 32px;
|
| 362 |
-
}
|
| 363 |
-
|
| 364 |
-
.gauge-title {
|
| 365 |
-
font-size: 38px;
|
| 366 |
-
font-weight: 700;
|
| 367 |
-
margin-bottom: 24px;
|
| 368 |
-
}
|
| 369 |
-
|
| 370 |
-
.gauge-nav {
|
| 371 |
-
display: flex;
|
| 372 |
-
flex-direction: column;
|
| 373 |
-
gap: 8px;
|
| 374 |
-
}
|
| 375 |
-
|
| 376 |
-
.gauge-nav-button {
|
| 377 |
-
border: 1px solid var(--border);
|
| 378 |
-
background: var(--surface);
|
| 379 |
-
color: var(--text-primary);
|
| 380 |
-
padding: 12px;
|
| 381 |
-
border-radius: 12px;
|
| 382 |
-
cursor: pointer;
|
| 383 |
-
text-align: left;
|
| 384 |
-
}
|
| 385 |
-
|
| 386 |
-
.gauge-card {
|
| 387 |
-
background: var(--surface);
|
| 388 |
-
border: 1px solid var(--border);
|
| 389 |
-
border-radius: 18px;
|
| 390 |
-
padding: 24px;
|
| 391 |
-
margin-bottom: 24px;
|
| 392 |
-
}
|
| 393 |
-
.gauge-input {
|
| 394 |
-
width: 100%;
|
| 395 |
-
max-width: 400px;
|
| 396 |
-
|
| 397 |
-
background: var(--bg-primary);
|
| 398 |
-
color: var(--text-primary);
|
| 399 |
-
|
| 400 |
-
border: 1px solid var(--border);
|
| 401 |
-
border-radius: 12px;
|
| 402 |
|
| 403 |
-
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
-
.gauge-
|
| 407 |
-
|
| 408 |
-
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
-
|
| 411 |
-
|
|
|
|
|
|
|
|
|
|
| 412 |
|
| 413 |
-
|
| 414 |
-
|
|
|
|
|
|
|
|
|
|
| 415 |
|
| 416 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
|
| 418 |
-
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
|
| 421 |
-
.gauge-
|
| 422 |
-
|
| 423 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
|
| 425 |
-
|
| 426 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
|
| 428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
|
| 430 |
-
|
| 431 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
`;
|
| 433 |
document.head.appendChild(style);
|
| 434 |
|
|
|
|
| 42 |
}
|
| 43 |
|
| 44 |
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 45 |
+
html, body, #root {
|
| 46 |
+
width: 100%;
|
| 47 |
+
min-height: 100vh;
|
| 48 |
+
min-height: 100dvh;
|
| 49 |
+
background: var(--bg-primary);
|
| 50 |
+
color: var(--text-primary);
|
| 51 |
+
-webkit-text-size-adjust: 100%;
|
| 52 |
+
}
|
| 53 |
+
body {
|
| 54 |
+
min-width: 0;
|
| 55 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 56 |
+
-webkit-font-smoothing: antialiased;
|
| 57 |
+
overflow-x: hidden;
|
| 58 |
+
}
|
| 59 |
+
button, input, textarea, select { font: inherit; color: inherit; }
|
| 60 |
|
| 61 |
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 62 |
::-webkit-scrollbar-track { background: transparent; }
|
| 63 |
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.12); border-radius: 9999px; }
|
| 64 |
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.22); }
|
| 65 |
|
|
|
|
| 66 |
@keyframes pilot-spin { to { transform: rotate(360deg); } }
|
| 67 |
|
| 68 |
.mobile-menu-button { display: none; }
|
|
|
|
| 74 |
word-break: break-word;
|
| 75 |
}
|
| 76 |
|
| 77 |
+
.pill-scroll-bar {
|
| 78 |
+
display: flex;
|
| 79 |
+
align-items: center;
|
| 80 |
+
gap: 8px;
|
| 81 |
+
overflow-x: auto;
|
| 82 |
+
-webkit-overflow-scrolling: touch;
|
| 83 |
+
scrollbar-width: none;
|
| 84 |
+
max-width: 100%;
|
| 85 |
+
padding-bottom: 2px;
|
| 86 |
+
}
|
| 87 |
+
.pill-scroll-bar::-webkit-scrollbar {
|
| 88 |
+
display: none;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.table-scroll-container {
|
| 92 |
+
width: 100%;
|
| 93 |
+
overflow-x: auto;
|
| 94 |
+
-webkit-overflow-scrolling: touch;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/* Responsive Breakpoints */
|
| 98 |
+
@media (max-width: 960px) {
|
| 99 |
.pilot-home {
|
| 100 |
width: 100% !important;
|
| 101 |
min-height: 100dvh !important;
|
| 102 |
+
height: auto !important;
|
| 103 |
overflow-y: auto !important;
|
| 104 |
+
display: flex !important;
|
| 105 |
+
flex-direction: column !important;
|
| 106 |
}
|
| 107 |
|
| 108 |
.pilot-home-topbar {
|
| 109 |
+
padding: 16px 20px !important;
|
| 110 |
align-items: flex-start !important;
|
| 111 |
+
gap: 12px !important;
|
| 112 |
flex-direction: column !important;
|
| 113 |
}
|
| 114 |
|
|
|
|
| 121 |
|
| 122 |
.pilot-home-center {
|
| 123 |
justify-content: flex-start !important;
|
| 124 |
+
padding: 24px 16px !important;
|
| 125 |
+
gap: 20px !important;
|
| 126 |
}
|
| 127 |
|
| 128 |
.pilot-home-grid {
|
| 129 |
width: 100% !important;
|
| 130 |
flex-direction: column !important;
|
| 131 |
+
gap: 14px !important;
|
| 132 |
+
align-items: center !important;
|
| 133 |
}
|
| 134 |
|
| 135 |
.pilot-product-card {
|
| 136 |
width: 100% !important;
|
| 137 |
+
max-width: 100% !important;
|
| 138 |
padding: 20px !important;
|
| 139 |
+
border-radius: 18px !important;
|
| 140 |
}
|
| 141 |
|
| 142 |
.pilot-home-footer {
|
| 143 |
+
padding: 16px 20px !important;
|
| 144 |
flex-direction: column !important;
|
| 145 |
+
gap: 6px !important;
|
| 146 |
+
text-align: center !important;
|
| 147 |
}
|
| 148 |
|
| 149 |
.auth-shell {
|
| 150 |
width: 100% !important;
|
| 151 |
min-height: 100dvh !important;
|
| 152 |
+
padding: 16px !important;
|
|
|
|
| 153 |
overflow-y: auto !important;
|
| 154 |
align-items: center !important;
|
| 155 |
}
|
| 156 |
|
| 157 |
.auth-panel {
|
| 158 |
width: 100% !important;
|
| 159 |
+
max-width: 440px !important;
|
| 160 |
+
padding: 28px 20px !important;
|
| 161 |
}
|
| 162 |
|
| 163 |
.auth-title {
|
| 164 |
+
font-size: 38px !important;
|
| 165 |
+
letter-spacing: -1.5px !important;
|
| 166 |
}
|
| 167 |
|
| 168 |
.docpilot-root,
|
|
|
|
| 177 |
display: inline-flex !important;
|
| 178 |
align-items: center !important;
|
| 179 |
justify-content: center !important;
|
| 180 |
+
min-width: 38px !important;
|
| 181 |
+
min-height: 38px !important;
|
| 182 |
+
padding: 6px 12px !important;
|
| 183 |
background: var(--surface) !important;
|
| 184 |
+
color: var(--text-primary) !important;
|
| 185 |
border: 1px solid var(--border) !important;
|
| 186 |
+
border-radius: 9999px !important;
|
| 187 |
cursor: pointer !important;
|
| 188 |
flex-shrink: 0 !important;
|
| 189 |
+
font-size: 15px !important;
|
| 190 |
}
|
| 191 |
|
| 192 |
.mobile-drawer-backdrop {
|
| 193 |
display: block !important;
|
| 194 |
position: fixed !important;
|
| 195 |
inset: 0 !important;
|
| 196 |
+
background: rgba(0, 0, 0, 0.65) !important;
|
| 197 |
+
backdrop-filter: blur(4px) !important;
|
| 198 |
border: 0 !important;
|
| 199 |
padding: 0 !important;
|
| 200 |
+
z-index: 998 !important;
|
| 201 |
}
|
| 202 |
|
| 203 |
.docpilot-sidebar,
|
| 204 |
.trace-sidebar {
|
| 205 |
position: fixed !important;
|
| 206 |
inset: 0 auto 0 0 !important;
|
| 207 |
+
width: min(85vw, 320px) !important;
|
| 208 |
max-width: 320px !important;
|
| 209 |
height: 100dvh !important;
|
| 210 |
+
z-index: 999 !important;
|
| 211 |
transform: translateX(-105%) !important;
|
| 212 |
+
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
| 213 |
+
box-shadow: 24px 0 60px rgba(0, 0, 0, 0.5) !important;
|
| 214 |
}
|
| 215 |
|
| 216 |
.docpilot-sidebar.is-open,
|
|
|
|
| 225 |
}
|
| 226 |
|
| 227 |
.docpilot-topbar {
|
| 228 |
+
padding: 10px 14px !important;
|
| 229 |
+
gap: 8px !important;
|
| 230 |
+
align-items: center !important;
|
| 231 |
+
flex-wrap: nowrap !important;
|
| 232 |
+
overflow-x: auto !important;
|
| 233 |
+
scrollbar-width: none !important;
|
| 234 |
+
}
|
| 235 |
+
.docpilot-topbar::-webkit-scrollbar {
|
| 236 |
+
display: none !important;
|
| 237 |
}
|
| 238 |
|
| 239 |
.docpilot-active-document {
|
| 240 |
min-width: 0 !important;
|
| 241 |
+
flex: 1 1 auto !important;
|
| 242 |
overflow: hidden !important;
|
| 243 |
text-overflow: ellipsis !important;
|
| 244 |
white-space: nowrap !important;
|
| 245 |
}
|
| 246 |
|
| 247 |
.docpilot-actions {
|
| 248 |
+
display: flex !important;
|
| 249 |
gap: 6px !important;
|
| 250 |
+
flex-wrap: nowrap !important;
|
| 251 |
+
flex-shrink: 0 !important;
|
| 252 |
}
|
| 253 |
|
| 254 |
+
.docpilot-actions button,
|
| 255 |
+
.docpilot-actions a {
|
| 256 |
+
padding: 6px 12px !important;
|
| 257 |
white-space: nowrap !important;
|
| 258 |
+
font-size: 11px !important;
|
| 259 |
}
|
| 260 |
|
| 261 |
.docpilot-chat-area {
|
| 262 |
+
padding: 16px 12px !important;
|
| 263 |
}
|
| 264 |
|
| 265 |
.docpilot-message-user,
|
| 266 |
.docpilot-message-assistant {
|
| 267 |
max-width: 100% !important;
|
| 268 |
+
font-size: 14px !important;
|
| 269 |
}
|
| 270 |
|
| 271 |
.docpilot-message-user {
|
| 272 |
+
padding: 10px 14px !important;
|
| 273 |
}
|
| 274 |
|
| 275 |
.docpilot-input-bar {
|
| 276 |
+
padding: 8px 10px !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
}
|
| 278 |
|
| 279 |
.trace-header {
|
| 280 |
+
padding: 14px 16px !important;
|
| 281 |
}
|
| 282 |
|
| 283 |
.trace-title {
|
| 284 |
+
font-size: 26px !important;
|
| 285 |
+
letter-spacing: -0.8px !important;
|
| 286 |
}
|
| 287 |
|
| 288 |
.trace-body {
|
| 289 |
display: block !important;
|
| 290 |
+
overflow-y: auto !important;
|
| 291 |
min-width: 0 !important;
|
| 292 |
}
|
| 293 |
|
| 294 |
.trace-detail-panel {
|
| 295 |
+
height: auto !important;
|
| 296 |
+
min-height: 100% !important;
|
| 297 |
+
padding: 14px !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
}
|
| 299 |
|
| 300 |
.trace-detail-header {
|
|
|
|
| 334 |
}
|
| 335 |
}
|
| 336 |
|
| 337 |
+
@media (max-width: 640px) {
|
| 338 |
.pilot-home-topbar h1 {
|
| 339 |
+
font-size: 24px !important;
|
| 340 |
}
|
| 341 |
|
| 342 |
.pilot-home-actions button,
|
| 343 |
+
.pilot-home-actions a,
|
| 344 |
+
.docpilot-actions button,
|
| 345 |
+
.docpilot-actions a {
|
| 346 |
+
font-size: 11px !important;
|
| 347 |
+
padding: 5px 10px !important;
|
| 348 |
}
|
| 349 |
|
| 350 |
.auth-title {
|
| 351 |
+
font-size: 32px !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
}
|
| 353 |
|
| 354 |
.trace-header {
|
| 355 |
+
padding: 12px 14px !important;
|
| 356 |
}
|
| 357 |
|
| 358 |
.trace-title {
|
| 359 |
+
font-size: 22px !important;
|
| 360 |
}
|
| 361 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
+
.gauge-root {
|
| 364 |
+
display: flex;
|
| 365 |
+
width: 100%;
|
| 366 |
+
min-height: 100vh;
|
| 367 |
+
min-height: 100dvh;
|
| 368 |
+
background: var(--bg-primary);
|
| 369 |
+
}
|
| 370 |
|
| 371 |
+
.gauge-sidebar {
|
| 372 |
+
width: 260px;
|
| 373 |
+
border-right: 1px solid var(--border);
|
| 374 |
+
padding: 24px;
|
| 375 |
+
background: var(--bg-secondary);
|
| 376 |
+
}
|
| 377 |
|
| 378 |
+
.gauge-main {
|
| 379 |
+
flex: 1;
|
| 380 |
+
overflow-y: auto;
|
| 381 |
+
padding: 32px;
|
| 382 |
+
}
|
| 383 |
|
| 384 |
+
.gauge-title {
|
| 385 |
+
font-size: 38px;
|
| 386 |
+
font-weight: 700;
|
| 387 |
+
margin-bottom: 24px;
|
| 388 |
+
}
|
| 389 |
|
| 390 |
+
.gauge-nav {
|
| 391 |
+
display: flex;
|
| 392 |
+
flex-direction: column;
|
| 393 |
+
gap: 8px;
|
| 394 |
+
}
|
| 395 |
|
| 396 |
+
.gauge-nav-button {
|
| 397 |
+
border: 1px solid var(--border);
|
| 398 |
+
background: var(--surface);
|
| 399 |
+
color: var(--text-primary);
|
| 400 |
+
padding: 12px;
|
| 401 |
+
border-radius: 12px;
|
| 402 |
+
cursor: pointer;
|
| 403 |
+
text-align: left;
|
| 404 |
+
}
|
| 405 |
|
| 406 |
+
.gauge-card {
|
| 407 |
+
background: var(--surface);
|
| 408 |
+
border: 1px solid var(--border);
|
| 409 |
+
border-radius: 18px;
|
| 410 |
+
padding: 24px;
|
| 411 |
+
margin-bottom: 24px;
|
| 412 |
+
}
|
| 413 |
|
| 414 |
+
.gauge-input {
|
| 415 |
+
width: 100%;
|
| 416 |
+
max-width: 400px;
|
| 417 |
+
background: var(--bg-primary);
|
| 418 |
+
color: var(--text-primary);
|
| 419 |
+
border: 1px solid var(--border);
|
| 420 |
+
border-radius: 12px;
|
| 421 |
+
padding: 12px 16px;
|
| 422 |
+
}
|
| 423 |
|
| 424 |
+
.gauge-textarea {
|
| 425 |
+
width: 100%;
|
| 426 |
+
min-height: 220px;
|
| 427 |
+
background: var(--bg-primary);
|
| 428 |
+
color: var(--text-primary);
|
| 429 |
+
border: 1px solid var(--border);
|
| 430 |
+
border-radius: 12px;
|
| 431 |
+
padding: 16px;
|
| 432 |
+
resize: vertical;
|
| 433 |
+
}
|
| 434 |
|
| 435 |
+
.gauge-button {
|
| 436 |
+
background: var(--surface-strong);
|
| 437 |
+
color: var(--text-primary);
|
| 438 |
+
border: 1px solid var(--border);
|
| 439 |
+
border-radius: 12px;
|
| 440 |
+
padding: 12px 18px;
|
| 441 |
+
cursor: pointer;
|
| 442 |
+
}
|
| 443 |
`;
|
| 444 |
document.head.appendChild(style);
|
| 445 |
|
frontend/src/tracepilot/TraceExplorer.jsx
CHANGED
|
@@ -276,7 +276,7 @@ export default function TraceExplorer({
|
|
| 276 |
|
| 277 |
|
| 278 |
{/* Action Toolbar */}
|
| 279 |
-
<div style={{ display: "flex", gap: "8px", alignItems: "center",
|
| 280 |
<a
|
| 281 |
href="/home"
|
| 282 |
onClick={(e) => {
|
|
|
|
| 276 |
|
| 277 |
|
| 278 |
{/* Action Toolbar */}
|
| 279 |
+
<div className="pill-scroll-bar" style={{ display: "flex", gap: "8px", alignItems: "center", overflowX: "auto", maxWidth: "100%", paddingBottom: "2px" }}>
|
| 280 |
<a
|
| 281 |
href="/home"
|
| 282 |
onClick={(e) => {
|
frontend/vercel.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"rewrites": [
|
| 3 |
+
{
|
| 4 |
+
"source": "/(.*)",
|
| 5 |
+
"destination": "/index.html"
|
| 6 |
+
}
|
| 7 |
+
]
|
| 8 |
+
}
|
vercel.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"rewrites": [
|
| 3 |
+
{
|
| 4 |
+
"source": "/(.*)",
|
| 5 |
+
"destination": "/index.html"
|
| 6 |
+
}
|
| 7 |
+
]
|
| 8 |
+
}
|