shak3008 commited on
Commit
69f317e
·
1 Parent(s): 95a23bc

feat: mobile responsive layouts, SPA direct routing rewrites, and Config 1/2/3 hover tooltips

Browse files
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
- height: "100vh",
259
  boxSizing: "border-box",
260
  display: "grid",
261
  gridTemplateRows: "auto 1fr auto",
262
- overflow: "hidden",
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: "12px" }}>
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: "32px", padding: "0 24px" }}>
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: "320px",
470
- padding: "28px 24px",
 
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: "24px", fontWeight: "800", color: accent, letterSpacing: "-0.6px", transition: "color 0.15s ease" }}>
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
- height: "100vh",
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: "12px" }}>
248
  <div
249
  style={{
250
- width: "34px",
251
- height: "34px",
252
  borderRadius: "10px",
253
  background: "linear-gradient(135deg, #3b82f6, #8b5cf6)",
254
  display: "flex",
255
  alignItems: "center",
256
  justifyContent: "center",
257
- fontSize: "18px",
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: "19px", fontWeight: 800, letterSpacing: "-0.5px", color: "#ffffff" }}>
266
  PilotMaster
267
  </div>
268
- <div style={{ fontSize: "11px", color: "#94a3b8", letterSpacing: "0.04em", textTransform: "uppercase", fontWeight: 600 }}>
269
- Observable AI Execution Ecosystem
270
  </div>
271
  </div>
272
  </div>
273
 
274
- <div style={{ display: "flex", alignItems: "center", gap: "14px" }}>
275
  <div
276
  style={{
277
  display: "flex",
278
  alignItems: "center",
279
- gap: "8px",
280
- padding: "5px 12px",
281
  borderRadius: "9999px",
282
  background: "rgba(34, 197, 94, 0.08)",
283
  border: "1px solid rgba(34, 197, 94, 0.2)",
284
- fontSize: "12px",
285
  color: "#4ade80",
286
  fontWeight: 600,
287
  }}
288
  >
289
  <span
290
  style={{
291
- width: "7px",
292
- height: "7px",
293
  borderRadius: "50%",
294
  background: "#22c55e",
295
  boxShadow: "0 0 8px #22c55e",
296
  }}
297
  />
298
- Kernel v2.0 Active
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: "7px 16px",
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: "12px",
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 Access
325
  </button>
326
  </div>
327
  </header>
328
 
329
- {/* MAIN HERO SPLIT VIEW (BALANCED VERTICAL ALIGNMENT) */}
330
  <main
331
  style={{
332
  flex: 1,
333
- display: "grid",
334
- gridTemplateColumns: "1.25fr 0.75fr",
335
- gap: "36px",
336
- padding: "28px 40px",
337
  maxWidth: "1400px",
338
  margin: "0 auto",
339
  width: "100%",
340
  boxSizing: "border-box",
341
  zIndex: 1,
342
- alignItems: "center",
343
  }}
344
  >
 
 
 
 
 
 
 
 
 
345
  {/* LEFT COLUMN: ARCHITECTURE TOPOLOGY & DEEP DIVE DESCRIPTION */}
346
- <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
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: "12px",
359
  color: "#93c5fd",
360
  fontWeight: 600,
361
- marginBottom: "10px",
 
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: "-1px",
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
- marginBottom: "16px",
 
 
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 10px",
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: "1fr 1.2fr 1fr",
439
- gap: "14px",
440
- alignItems: "center",
441
- marginBottom: "16px",
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: "14px",
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: "16px" }}>📄</span>
467
- <h3 style={{ margin: 0, fontSize: "14px", fontWeight: 800, color: "#60a5fa" }}>DocPilot</h3>
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: "16px 12px",
481
- borderRadius: "18px",
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
- gap: "6px",
 
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: "20px" }}>⚡</div>
514
  <div>
515
- <h3 style={{ margin: 0, fontSize: "15px", fontWeight: 900, color: "#38bdf8" }}>PilotCore</h3>
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: "14px 16px",
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: "12.5px", lineHeight: 1.6, color: "#cbd5e1" }}>
595
  {currentDesc.description}
596
  </p>
597
- <div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
598
  {currentDesc.highlights.map((h, idx) => (
599
  <span
600
  key={idx}
601
  style={{
602
- fontSize: "11px",
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: "400px",
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
- </main>
 
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
- fontSize: "11.5px",
 
 
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: "18px" }}>
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: "300px",
 
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
- flexWrap: "wrap",
 
1925
  }}
1926
  >
1927
- <div style={{ display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }}>
 
 
 
 
 
 
 
 
 
 
 
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: "350px",
 
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: "300px",
 
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
- export default function OverallTable({ data }) {
 
 
 
 
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 formatConfigName = (name) =>
22
- name?.replaceAll("_", " ").replaceAll("+", " + ")
23
- .replace("NoRewrite", "(No Rewrite)").replace("NoReranker", "(No Reranker)") ?? "";
 
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
- <span style={{ fontWeight: i === 0 ? 600 : 400 }}>{formatConfigName(row.config_name)}</span>
 
 
 
 
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
- <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
153
- {i === 0 && (
154
- <span style={{
155
- fontSize: "10px", fontWeight: 700, padding: "2px 6px",
156
- borderRadius: "4px", background: "rgba(245,158,11,0.15)",
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
- key: "faithfulness",
6
- label: "Faithfulness",
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
- let score =
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
- style={{
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
- ({ key }) => {
82
- const values = rows.map(
83
- (r) => r[key]
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, rowIndex) => {
100
- METRICS.forEach(
101
- ({ key, label }, colIndex) => {
102
- const value = row[key];
103
-
104
- annotations.push({
105
- x: label,
106
- y:
107
- row.config
108
- ?.replaceAll("_", " ")
109
- ?.replaceAll("+", " + ") ??
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
- (m) => m.label
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
- 120 +
159
- rows.length * 70,
160
- margin: {
161
- l: 220,
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
- width: "100%",
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 t =
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
- if (rows.length <= 7) {
67
- return rows;
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
- point[row.config] =
93
- normalizers[key](row[key]) ?? null;
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
- map[row.config] = {};
106
-
 
 
 
107
  METRICS.forEach(({ key }) => {
108
- map[row.config][key] = row[key];
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
- "rgba(15,15,25,0.96)",
133
- border:
134
- "1px solid rgba(255,255,255,0.12)",
135
- borderRadius: "12px",
136
- padding: "12px 14px",
137
  fontSize: "12px",
138
- minWidth: "200px",
 
139
  }}
140
  >
141
- <div
142
- style={{
143
- color: "white",
144
- fontWeight: 700,
145
- marginBottom: "8px",
146
- }}
147
- >
148
- {label}
149
  </div>
150
 
151
- {payload.map((entry) => (
152
- <div
153
- key={entry.dataKey}
154
- style={{
155
- display: "flex",
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
- <span style={{ color: "white" }}>
170
- {rawByConfigAndMetric[
171
- entry.dataKey
172
- ]?.[metricKey] != null
173
- ? Number(
174
- rawByConfigAndMetric[
175
- entry.dataKey
176
- ][metricKey]
177
- ).toFixed(2)
178
- : "—"}
179
- </span>
180
- </div>
181
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
182
  </div>
183
  );
184
  };
185
 
186
- if (!displayRows.length) {
187
  return (
188
- <p
189
- style={{
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
- {rows.length > 7 && (
204
- <div
205
- style={{
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
- style={{
276
- color:
277
- "rgba(255,255,255,0.7)",
278
- }}
279
- >
280
- {value.replaceAll(
281
- "_",
282
- " "
283
- )}
284
  </span>
285
  )}
286
  />
287
-
288
- {displayRows
289
- .sort(
290
- (a, b) =>
291
- (a.average_rank ?? 999) -
292
- (b.average_rank ?? 999)
293
- )
294
- .map((row, i) => (
295
  <Line
296
- key={row.config}
297
  type="monotone"
298
- name={row.config}
299
- dataKey={row.config}
300
  stroke={getColor(i)}
301
  strokeWidth={3}
302
- dot={{
303
- r: 5,
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
- style={{
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) => ({ config: row.config, x: row[xMetric], y: row[yMetric] })),
 
 
 
 
 
 
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,15,25,0.95)", border: "1px solid rgba(255,255,255,0.12)",
62
- borderRadius: "10px", padding: "10px 14px", fontSize: "12px",
 
 
 
 
 
63
  }}>
64
- <div style={{ fontWeight: 700, color: "white", marginBottom: "4px" }}>
65
- {p.config?.replaceAll("_", " ")}
66
- </div>
67
- <div style={{ color: "rgba(255,255,255,0.6)" }}>
68
- {metricLabel(xMetric)}: <span style={{ color: "white" }}>{Number(p.x).toFixed(2)}</span>
 
 
 
 
 
 
 
69
  </div>
70
- <div style={{ color: "rgba(255,255,255,0.6)" }}>
71
- {metricLabel(yMetric)}: <span style={{ color: "white" }}>{Number(p.y).toFixed(2)}</span>
 
 
72
  </div>
73
- <div style={{ marginTop: "4px", fontWeight: 600, color: p.dominated ? "rgba(255,255,255,0.4)" : "#22c55e" }}>
74
- {p.dominated ? "Dominated" : "Pareto optimal"}
 
 
 
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 optimal", type: "circle", color: accent },
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} fillOpacity={0.6} />
117
- <Scatter data={frontier} fill={accent} line={{ stroke: accent, strokeWidth: 1.5, strokeDasharray: "4 4" }} lineType="joint" />
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) => ({ config: r.config, wins: wins[r.config] ?? 0 }))
 
 
 
 
 
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,15,25,0.95)", border: "1px solid rgba(255,255,255,0.12)",
45
- borderRadius: "10px", padding: "10px 14px", fontSize: "12px",
 
 
 
 
 
46
  }}>
47
- <div style={{ fontWeight: 700, color: "white", marginBottom: "4px" }}>
48
- {p.config?.replaceAll("_", " ")}
 
 
 
49
  </div>
50
- <div style={{ color: "rgba(255,255,255,0.6)" }}>
51
- Metrics won: <span style={{ color: "white" }}>{p.wins} / {METRICS.length}</span>
 
 
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="config" tickFormatter={(v) => v?.replaceAll("_", " ")}
77
- stroke="rgba(255,255,255,0.35)" tick={{ fill: "rgba(255,255,255,0.5)", fontSize: 11 }}
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 the lowest value).
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
- return null;
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
- if (rows.length <= 7) {
74
- return rows;
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
- displayRows.forEach(
103
- (row) => {
104
- point[row.config] =
105
- normalizers[key](
106
- row[key]
107
- ) ?? 0;
108
- }
109
- );
110
-
111
- return point;
112
- }
113
- ),
114
- [displayRows, normalizers]
115
- );
116
 
117
  const rawMap = useMemo(() => {
118
  const map = {};
119
-
120
  displayRows.forEach((row) => {
121
- map[row.config] = {};
122
-
123
- METRICS.forEach(
124
- ({ key }) => {
125
- map[row.config][key] =
126
- row[key];
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
- "rgba(15,15,25,0.97)",
157
- border:
158
- "1px solid rgba(255,255,255,0.12)",
159
- borderRadius: 12,
160
- padding:
161
- "12px 14px",
162
  fontSize: 12,
163
- minWidth: 220,
 
164
  }}
165
  >
166
- <div
167
- style={{
168
- color: "white",
169
- fontWeight: 700,
170
- marginBottom: 10,
171
- }}
172
- >
173
- {label}
174
  </div>
175
 
176
- {payload.map((entry) => (
177
- <div
178
- key={entry.dataKey}
179
- style={{
180
- display: "flex",
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
- color:
212
- "rgba(255,255,255,0.55)",
213
- fontSize: 11,
 
214
  }}
215
  >
216
- Raw:{" "}
217
- {rawMap[
218
- entry.dataKey
219
- ]?.[
220
- metricKey
221
- ] != null
222
- ? Number(
223
- rawMap[
224
- entry
225
- .dataKey
226
- ][
227
- metricKey
228
- ]
229
- ).toFixed(
230
- 2
231
- )
232
- : "—"}
233
  </div>
234
- </div>
235
- </div>
236
- ))}
237
  </div>
238
  );
239
  };
240
 
241
- if (!displayRows.length) {
242
  return (
243
- <p
244
- style={{
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
- {rows.length > 7 && (
260
- <div
261
- style={{
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={90}
296
  domain={[0, 100]}
297
- tick={{
298
- fill:
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
- value
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
- (row, i) => (
 
334
  <Radar
335
- key={row.config}
336
- name={
337
- row.config
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
- style={{
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) => ({ config: r.config, averageRank: r.averageRank }))
17
- // Lower average rank is better, so the best configuration sorts first.
 
 
 
 
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,15,25,0.95)", border: "1px solid rgba(255,255,255,0.12)",
41
- borderRadius: "10px", padding: "10px 14px", fontSize: "12px",
 
 
 
 
 
42
  }}>
43
- <div style={{ fontWeight: 700, color: "white", marginBottom: "4px" }}>
44
- {p.config?.replaceAll("_", " ")}
 
45
  </div>
46
- <div style={{ color: "rgba(255,255,255,0.6)" }}>
47
- Average rank: <span style={{ color: "white" }}>{Number(p.averageRank).toFixed(2)}</span>
 
 
 
 
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="config" tickFormatter={(v) => v?.replaceAll("_", " ")}
68
- stroke="rgba(255,255,255,0.35)" tick={{ fill: "rgba(255,255,255,0.5)", fontSize: 11 }}
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) => ({ config: row.config, x: row[xMetric], y: row[yMetric] })),
 
 
 
 
 
 
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,15,25,0.95)", border: "1px solid rgba(255,255,255,0.12)",
37
- borderRadius: "10px", padding: "10px 14px", fontSize: "12px",
 
 
 
 
 
38
  }}>
39
- <div style={{ fontWeight: 700, color: "white", marginBottom: "4px" }}>
40
- {p.config?.replaceAll("_", " ")}
41
  </div>
42
- <div style={{ color: "rgba(255,255,255,0.6)" }}>
43
- {metricLabel(xMetric)}: <span style={{ color: "white" }}>{Number(p.x).toFixed(2)}</span>
 
 
44
  </div>
45
- <div style={{ color: "rgba(255,255,255,0.6)" }}>
46
- {metricLabel(yMetric)}: <span style={{ color: "white" }}>{Number(p.y).toFixed(2)}</span>
 
 
 
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
- // Normalizes the leaderboard payload (separate ranked arrays per metric —
2
- // overall / faithfulness / grounding / retrieval_quality / query_coverage /
3
- // latency, each shaped like [{ config_name, value, ... }]) into one flat row
4
- // per configuration:
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)) configMap.set(name, { config: name });
 
 
 
 
 
 
50
  return configMap.get(name);
51
  };
52
 
53
- // Seed every config that appears anywhere in the leaderboard first, so a
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: "1fr 1fr", gap: "16px" }}>
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: "350px",
 
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(360px, 1fr))",
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(4, 1fr)", gap: "12px", marginBottom: "16px" }}>
123
  {[
124
- { label: "Best Overall", value: formatName(bestConfig), icon: "🥇", color: "#f59e0b" },
125
- { label: "Fastest Config", value: formatName(fastestConfig), icon: "⚡", color: "#22c55e" },
126
- { label: "Top Faithfulness", value: formatName(topFaithfulness), icon: "📋", color: accent },
127
- { label: "Configs Evaluated", value: totalConfigs, icon: "🔬", color: "#a78bfa" },
128
- ].map(({ label, value, icon, color }) => (
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
- <div style={{
150
- fontSize: "14px", fontWeight: 700, color: "white", lineHeight: 1.2,
151
- whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
152
- }}>{value}</div>
153
- <div style={{ fontSize: "11px", color: "rgba(255,255,255,0.35)", marginTop: "3px" }}>{label}</div>
 
 
 
 
 
 
 
 
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 { width: 100%; height: 100%; overflow: hidden; background: var(--bg-primary); color: var(--text-primary); }
46
- body { min-width: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; -webkit-font-smoothing: antialiased; }
47
- button, input, textarea { font: inherit; color: inherit; }
 
 
 
 
 
 
 
 
 
 
 
 
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
- @media (max-width: 900px) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  .pilot-home {
68
  width: 100% !important;
69
  min-height: 100dvh !important;
70
- height: 100dvh !important;
71
  overflow-y: auto !important;
 
 
72
  }
73
 
74
  .pilot-home-topbar {
75
- padding: 18px 18px !important;
76
  align-items: flex-start !important;
77
- gap: 14px !important;
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: 28px 16px !important;
91
- gap: 18px !important;
92
  }
93
 
94
  .pilot-home-grid {
95
  width: 100% !important;
96
  flex-direction: column !important;
97
- gap: 12px !important;
 
98
  }
99
 
100
  .pilot-product-card {
101
  width: 100% !important;
 
102
  padding: 20px !important;
103
- border-radius: 12px !important;
104
  }
105
 
106
  .pilot-home-footer {
107
- padding: 12px 18px !important;
108
  flex-direction: column !important;
109
- gap: 5px !important;
 
110
  }
111
 
112
  .auth-shell {
113
  width: 100% !important;
114
  min-height: 100dvh !important;
115
- height: 100dvh !important;
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: 500px !important;
 
124
  }
125
 
126
  .auth-title {
127
- font-size: 46px !important;
128
- letter-spacing: -2px !important;
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: 36px !important;
144
- height: 36px !important;
145
- padding: 0 !important;
146
  background: var(--surface) !important;
147
- color: var(--text-secondary) !important;
148
  border: 1px solid var(--border) !important;
149
- border-radius: 8px !important;
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.52) !important;
 
159
  border: 0 !important;
160
  padding: 0 !important;
161
- z-index: 19 !important;
162
  }
163
 
164
  .docpilot-sidebar,
165
  .trace-sidebar {
166
  position: fixed !important;
167
  inset: 0 auto 0 0 !important;
168
- width: min(86vw, 320px) !important;
169
  max-width: 320px !important;
170
  height: 100dvh !important;
171
- z-index: 20 !important;
172
  transform: translateX(-105%) !important;
173
- transition: transform 0.2s ease !important;
174
- box-shadow: 24px 0 60px rgba(0, 0, 0, 0.35) !important;
175
  }
176
 
177
  .docpilot-sidebar.is-open,
@@ -186,93 +225,76 @@ style.textContent = `
186
  }
187
 
188
  .docpilot-topbar {
189
- padding: 10px 12px !important;
190
- gap: 10px !important;
191
- align-items: flex-start !important;
192
- flex-wrap: wrap !important;
 
 
 
 
 
193
  }
194
 
195
  .docpilot-active-document {
196
  min-width: 0 !important;
197
- flex: 1 1 180px !important;
198
  overflow: hidden !important;
199
  text-overflow: ellipsis !important;
200
  white-space: nowrap !important;
201
  }
202
 
203
  .docpilot-actions {
204
- width: 100% !important;
205
  gap: 6px !important;
206
- overflow-x: auto !important;
207
- padding-bottom: 2px !important;
208
  }
209
 
210
- .docpilot-actions button {
211
- padding: 8px 10px !important;
 
212
  white-space: nowrap !important;
 
213
  }
214
 
215
  .docpilot-chat-area {
216
- padding: 20px 14px !important;
217
  }
218
 
219
  .docpilot-message-user,
220
  .docpilot-message-assistant {
221
  max-width: 100% !important;
222
- font-size: 15px !important;
223
  }
224
 
225
  .docpilot-message-user {
226
- padding: 12px 14px !important;
227
  }
228
 
229
  .docpilot-input-bar {
230
- padding: 12px !important;
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: 20px 16px 14px !important;
250
  }
251
 
252
  .trace-title {
253
- font-size: 34px !important;
254
- letter-spacing: -1px !important;
255
  }
256
 
257
  .trace-body {
258
  display: block !important;
259
- overflow: hidden !important;
260
  min-width: 0 !important;
261
  }
262
 
263
  .trace-detail-panel {
264
- height: 100% !important;
265
- padding: 1rem !important;
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: 430px) {
316
  .pilot-home-topbar h1 {
317
- font-size: 30px !important;
318
  }
319
 
320
  .pilot-home-actions button,
321
- .docpilot-actions button {
322
- font-size: 12px !important;
 
 
 
323
  }
324
 
325
  .auth-title {
326
- font-size: 40px !important;
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: 18px 14px 12px !important;
339
  }
340
 
341
  .trace-title {
342
- font-size: 30px !important;
343
  }
344
- }.gauge-root {
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
- padding: 12px 16px;
404
- }
 
 
 
 
 
405
 
406
- .gauge-textarea {
407
- width: 100%;
408
- min-height: 220px;
 
 
 
409
 
410
- background: var(--bg-primary);
411
- color: var(--text-primary);
 
 
 
412
 
413
- border: 1px solid var(--border);
414
- border-radius: 12px;
 
 
 
415
 
416
- padding: 16px;
 
 
 
 
417
 
418
- resize: vertical;
419
- }
 
 
 
 
 
 
 
420
 
421
- .gauge-button {
422
- background: var(--surface-strong);
423
- color: var(--text-primary);
 
 
 
 
424
 
425
- border: 1px solid var(--border);
426
- border-radius: 12px;
 
 
 
 
 
 
 
427
 
428
- padding: 12px 18px;
 
 
 
 
 
 
 
 
 
429
 
430
- cursor: pointer;
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", flexWrap: "wrap" }}>
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
+ }