chordia-ui 3.4.2 → 3.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/Timeline.cjs.js +6 -1
  2. package/dist/Timeline.cjs.js.map +1 -1
  3. package/dist/Timeline.es.js +597 -129
  4. package/dist/Timeline.es.js.map +1 -1
  5. package/dist/UpdatedInteractionRecording.cjs.js +1 -1
  6. package/dist/UpdatedInteractionRecording.cjs.js.map +1 -1
  7. package/dist/UpdatedInteractionRecording.es.js +233 -241
  8. package/dist/UpdatedInteractionRecording.es.js.map +1 -1
  9. package/dist/components/UpdatedInteractionDetails.cjs.js +2 -2
  10. package/dist/components/UpdatedInteractionDetails.cjs.js.map +1 -1
  11. package/dist/components/UpdatedInteractionDetails.es.js +451 -360
  12. package/dist/components/UpdatedInteractionDetails.es.js.map +1 -1
  13. package/dist/components/media.cjs.js +1 -1
  14. package/dist/components/media.cjs.js.map +1 -1
  15. package/dist/components/media.es.js +8 -9
  16. package/dist/components/media.es.js.map +1 -1
  17. package/dist/index.cjs.js +1 -1
  18. package/dist/index.cjs2.js +1 -1
  19. package/dist/index.cjs2.js.map +1 -1
  20. package/dist/index.es.js +52 -53
  21. package/dist/index.es.js.map +1 -1
  22. package/dist/index.es2.js +596 -587
  23. package/dist/index.es2.js.map +1 -1
  24. package/dist/pages/interactionDetails.cjs.js +2 -2
  25. package/dist/pages/interactionDetails.cjs.js.map +1 -1
  26. package/dist/pages/interactionDetails.es.js +16 -17
  27. package/dist/pages/interactionDetails.es.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/components/UpdatedInteractionDetails/UpdatedCompassScore.jsx +54 -2
  30. package/src/components/UpdatedInteractionDetails/UpdatedInteractionDetails.jsx +58 -14
  31. package/src/components/UpdatedInteractionDetails/UpdatedInteractionRecording.jsx +132 -126
  32. package/src/components/UpdatedInteractionDetails/UpdatedInteractionSignals.jsx +14 -6
  33. package/src/components/login/LoginPage.jsx +18 -3
  34. package/dist/TranscriptCard.cjs.js +0 -7
  35. package/dist/TranscriptCard.cjs.js.map +0 -1
  36. package/dist/TranscriptCard.es.js +0 -474
  37. package/dist/TranscriptCard.es.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chordia-ui",
3
- "version": "3.4.2",
3
+ "version": "3.4.4",
4
4
  "description": "Chordia Design System - UI components, tokens, and Tailwind preset",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { Info } from 'lucide-react';
3
3
 
4
4
  /*
@@ -97,6 +97,56 @@ const ScoreBar = ({ value = 0, maxValue = 5 }) => (
97
97
  </div>
98
98
  );
99
99
 
100
+ const LEGEND_TOOLTIPS = {
101
+ 'CSAT': 'Predicted Customer Satisfaction',
102
+ 'Resolution': 'Predicted Resolution: was the issue fully resolved?',
103
+ 'Process Adherence': 'Process Adherence: Did the agent follow procedures?',
104
+ 'Communication': 'Predicted Communication Quality: Clarity, Empathy, Professionalism',
105
+ };
106
+
107
+ const InfoTooltip = ({ text, children }) => {
108
+ const [show, setShow] = useState(false);
109
+ return (
110
+ <div
111
+ style={{ position: 'relative', display: 'inline-flex' }}
112
+ onMouseEnter={() => setShow(true)}
113
+ onMouseLeave={() => setShow(false)}
114
+ >
115
+ {children}
116
+ {show && (
117
+ <div style={{
118
+ position: 'absolute',
119
+ bottom: '100%',
120
+ left: '50%',
121
+ transform: 'translateX(-50%)',
122
+ marginBottom: 6,
123
+ display: 'flex',
124
+ width: 160,
125
+ padding: 10,
126
+ alignItems: 'center',
127
+ gap: 10,
128
+ borderRadius: 4,
129
+ border: '1px solid var(--Grey-absent, #D9D9D9)',
130
+ background: 'var(--Grey-Strong, #2E3236)',
131
+ zIndex: 20,
132
+ pointerEvents: 'none',
133
+ }}>
134
+ <span style={{
135
+ width: 140,
136
+ flexShrink: 0,
137
+ color: '#FFF',
138
+ fontSize: 12,
139
+ fontWeight: 400,
140
+ lineHeight: 'normal',
141
+ }}>
142
+ {text}
143
+ </span>
144
+ </div>
145
+ )}
146
+ </div>
147
+ );
148
+ };
149
+
100
150
  const LegendItem = ({ label, value = 0, maxValue = 5 }) => (
101
151
  <div style={{
102
152
  display: 'flex',
@@ -119,7 +169,9 @@ const LegendItem = ({ label, value = 0, maxValue = 5 }) => (
119
169
  }}>
120
170
  {label}
121
171
  </span>
122
- <Info size={14} color={COLORS.infoIcon} strokeWidth={1} />
172
+ <InfoTooltip text={LEGEND_TOOLTIPS[label] || label}>
173
+ <Info size={14} color={COLORS.infoIcon} strokeWidth={1} style={{ cursor: 'pointer' }} />
174
+ </InfoTooltip>
123
175
  </div>
124
176
  <ScoreBar value={value} maxValue={maxValue} />
125
177
  </div>
@@ -43,6 +43,17 @@ const UpdatedInteractionDetails = ({
43
43
  turnObservations,
44
44
  highlightedTurns,
45
45
  onTurnPlayPause,
46
+ // Signal evidence playback — host app can provide to play audio segments from signals
47
+ onPlayEvidence,
48
+ onHighlightTurns,
49
+ // Compass score props
50
+ compassScore,
51
+ predictedCsat,
52
+ // Customer session dropdown
53
+ customerSessions: customerSessionsList,
54
+ customerSessionCount,
55
+ onSessionClick,
56
+ onViewAllSessions,
46
57
  // Footer navigation
47
58
  prevSessionTitle,
48
59
  prevSessionDesc,
@@ -55,6 +66,8 @@ const UpdatedInteractionDetails = ({
55
66
  const [expandedSignals, setExpandedSignals] = useState(new Set());
56
67
  const [showSessionDropdown, setShowSessionDropdown] = useState(false);
57
68
  const [hoveredSessionIdx, setHoveredSessionIdx] = useState(null);
69
+ const [internalPlaying, setInternalPlaying] = useState(false);
70
+ const [internalCurrentTime, setInternalCurrentTime] = useState(0);
58
71
  const recordingRef = useRef(null);
59
72
 
60
73
  // Section refs for scroll-to-section tab behavior
@@ -72,23 +85,42 @@ const UpdatedInteractionDetails = ({
72
85
  }
73
86
  };
74
87
 
75
- const sessionHistory = [
76
- { title: 'Tire Repair Appointment', date: '11/12/2025, 2:30:00 PM' },
77
- { title: 'Quote Confirmation', date: '10/1/2025, 9:00:00 AM' },
78
- { title: 'Automative Repair Enquiry', date: '09/15/2025, 11:45:00 AM' },
79
- { title: 'Vehicle Inspection Reminder', date: '07/20/2025, 1:00:00 PM' },
80
- { title: 'Oil Change Service', date: '05/5/2025, 10:00:00 AM' },
81
- ];
88
+ const sessionHistory = customerSessionsList || [];
82
89
 
83
90
  /* Called from Signals "Show in transcript" icon — seeks recording to that time */
84
91
  const handleShowInTranscript = (startMs) => {
85
92
  const timeSec = startMs / 1000;
86
- // Call the recording component's seek via ref
87
93
  if (recordingRef.current?.seekTo) {
88
94
  recordingRef.current.seekTo(timeSec);
89
95
  }
90
96
  };
91
97
 
98
+ /* Called from Signals evidence play button — seeks + plays audio segment */
99
+ const handlePlayEvidence = (ev) => {
100
+ if (onPlayEvidence) {
101
+ onPlayEvidence(ev);
102
+ } else {
103
+ // Default: seek to evidence start time via recording ref
104
+ const startMs = ev.start_ms ?? ev.startMs;
105
+ if (startMs != null && recordingRef.current?.seekTo) {
106
+ recordingRef.current.seekTo(startMs / 1000);
107
+ setInternalPlaying(true);
108
+ setInternalCurrentTime(startMs / 1000);
109
+ // Stop playing after evidence duration
110
+ const endMs = ev.end_ms ?? ev.endMs ?? (startMs + 5000);
111
+ const durationMs = endMs - startMs;
112
+ setTimeout(() => setInternalPlaying(false), durationMs);
113
+ }
114
+ }
115
+ };
116
+
117
+ /* Called to highlight transcript turns related to evidence */
118
+ const handleHighlightTurns = (turnIds) => {
119
+ if (onHighlightTurns) {
120
+ onHighlightTurns(turnIds);
121
+ }
122
+ };
123
+
92
124
  const toggleSignal = (key) => {
93
125
  setExpandedSignals((prev) => {
94
126
  const next = new Set(prev);
@@ -162,7 +194,7 @@ const UpdatedInteractionDetails = ({
162
194
 
163
195
  const agentName = externalAgentName || 'Agent';
164
196
  const customerName = externalCustomerName || 'Customer';
165
- const customerSessions = 8;
197
+ const sessionCount = customerSessionCount ?? sessionHistory.length;
166
198
  const dateStr = meta.evaluated_dt ? new Date(meta.evaluated_dt).toLocaleString() : '3/29/2026, 8:30:00 AM';
167
199
  const direction = demoCallPurpose.interaction_direction === 'inbound' ? 'Inbound' : 'Outbound';
168
200
 
@@ -315,7 +347,7 @@ const UpdatedInteractionDetails = ({
315
347
  >
316
348
  <CircleUser size={16} color="var(--Grey-Muted, #808183)" strokeWidth={1.5} />
317
349
  <span style={{ fontSize: 13, fontWeight: 400, color: 'var(--Grey-Strong, #2E3236)', lineHeight: 1.2 }}>
318
- {customerName} ({customerSessions} Sessions)
350
+ {customerName} ({sessionCount} Sessions)
319
351
  </span>
320
352
  {showSessionDropdown
321
353
  ? <ChevronUp size={16} color="var(--Grey-Muted, #808183)" strokeWidth={1.5} />
@@ -358,7 +390,7 @@ const UpdatedInteractionDetails = ({
358
390
  color: 'var(--Grey-Muted, #808183)',
359
391
  lineHeight: 1,
360
392
  }}>
361
- {customerSessions} Sessions in past
393
+ {sessionCount} Sessions in past
362
394
  </span>
363
395
  </div>
364
396
  </div>
@@ -366,7 +398,8 @@ const UpdatedInteractionDetails = ({
366
398
  {/* Session list — Figma: 264px, pad 12 16, space-between, center */}
367
399
  {sessionHistory.map((session, i) => (
368
400
  <div
369
- key={i}
401
+ key={session.id || i}
402
+ onClick={() => { if (onSessionClick) onSessionClick(session); setShowSessionDropdown(false); }}
370
403
  onMouseEnter={() => setHoveredSessionIdx(i)}
371
404
  onMouseLeave={() => setHoveredSessionIdx(null)}
372
405
  style={{
@@ -413,7 +446,9 @@ const UpdatedInteractionDetails = ({
413
446
  display: 'flex',
414
447
  justifyContent: 'center',
415
448
  }}>
416
- <button style={{
449
+ <button
450
+ onClick={() => { if (onViewAllSessions) onViewAllSessions(); setShowSessionDropdown(false); }}
451
+ style={{
417
452
  display: 'flex',
418
453
  height: 32,
419
454
  padding: '16px 16px 16px 12px',
@@ -453,7 +488,12 @@ const UpdatedInteractionDetails = ({
453
488
  }}>
454
489
  {/* Left — Compass Score */}
455
490
  <div style={{ flex: 1, minWidth: 0 }}>
456
- <UpdatedCompassScore />
491
+ <UpdatedCompassScore
492
+ score={compassScore ?? 0}
493
+ maxScore={100}
494
+ predictedScore={predictedCsat ?? 0}
495
+ predictedLabel="Predicted Objective"
496
+ />
457
497
  </div>
458
498
 
459
499
  {/* Right — Context grid + Agent Lift Analysis stacked */}
@@ -526,7 +566,11 @@ const UpdatedInteractionDetails = ({
526
566
  signals={demoSignals}
527
567
  expandedSignals={expandedSignals}
528
568
  toggleSignal={toggleSignal}
569
+ playEvidence={handlePlayEvidence}
570
+ highlightTurns={handleHighlightTurns}
529
571
  onShowInTranscript={handleShowInTranscript}
572
+ timelinePlaying={timelinePlaying || internalPlaying}
573
+ currentTimeSeconds={currentTimeSeconds ?? internalCurrentTime}
530
574
  />
531
575
  </div>
532
576
  <div style={{ flex: 1, minWidth: 0 }}>
@@ -5,7 +5,6 @@ import {
5
5
  AudioLines, PlayCircle, PauseCircle,
6
6
  FileSignal,
7
7
  } from 'lucide-react';
8
- import Timeline from '../media/Timeline.jsx';
9
8
 
10
9
  function fmtTime(seconds) {
11
10
  const m = Math.floor(seconds / 60);
@@ -222,7 +221,13 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
222
221
  useImperativeHandle(ref, () => ({
223
222
  seekTo: (timeSec) => {
224
223
  handleSeek(timeSec);
225
- if (selfManaged) {
224
+ if (parentManaged) {
225
+ // Parent manages audio — seek was already called via handleSeek → externalOnSeek
226
+ // Now trigger play if not already playing
227
+ if (!externalPlaying && externalOnTogglePlay) {
228
+ externalOnTogglePlay();
229
+ }
230
+ } else if (selfManaged) {
226
231
  const audio = internalAudioRef.current;
227
232
  if (audio) {
228
233
  audio.play().then(() => {
@@ -465,91 +470,86 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
465
470
  </div>
466
471
  </div>
467
472
 
468
- {/* ── Row 2: Progress bar ── */}
469
- {audioUrl && parentManaged ? (
470
- <>
471
- <Timeline
472
- segments={timelineSegments}
473
- durationSeconds={durationSeconds}
474
- currentTimeSeconds={externalCurrentTime}
475
- onSeek={externalOnSeek}
476
- showControls={false}
477
- hasRecording
478
- timelinePlaying={externalPlaying}
479
- playbackRate={externalRate}
480
- />
481
- <audio ref={externalAudioRef} preload="none" style={{ display: 'none' }}>
482
- <source src={audioUrl} type="audio/mpeg" />
483
- </audio>
484
- </>
485
- ) : (
486
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
487
- {/* Time label + scrubber bar */}
488
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
489
- <span style={{
490
- fontSize: 13, fontWeight: 600,
491
- color: progress > 0 ? 'var(--Green-Primary, #00925F)' : 'var(--Grey-Strong, #2E3236)',
492
- fontFamily: 'var(--font-sans)',
493
- lineHeight: 1.2,
494
- minWidth: 60,
495
- }}>
496
- {fmtTime(displayTime)}
497
- </span>
498
- <div
499
- onClick={(e) => {
500
- const rect = e.currentTarget.getBoundingClientRect();
501
- const clickX = e.clientX - rect.left;
502
- const pct = Math.max(0, Math.min(1, clickX / rect.width));
503
- handleSeek(pct * effectiveDuration);
504
- }}
473
+ {/* ── Row 2: Progress bar (V4 style for all modes) ── */}
474
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
475
+ {/* Time label + scrubber bar — same layout as speaker rows: 60px label + 8px gap + flex bar */}
476
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
477
+ <span style={{
478
+ fontSize: 13, fontWeight: 500,
479
+ color: 'var(--Grey-Strong, #2E3236)',
480
+ fontFamily: 'var(--font-sans)',
481
+ lineHeight: 1.2,
482
+ minWidth: 60,
483
+ }}>
484
+ {fmtTime(effectiveDuration)}
485
+ </span>
486
+ <div
487
+ onClick={(e) => {
488
+ const rect = e.currentTarget.getBoundingClientRect();
489
+ const clickX = e.clientX - rect.left;
490
+ const pct = Math.max(0, Math.min(1, clickX / rect.width));
491
+ handleSeek(pct * effectiveDuration);
492
+ }}
493
+ style={{
494
+ flex: 1, height: 16,
495
+ position: 'relative',
496
+ display: 'flex', alignItems: 'center',
497
+ cursor: 'pointer',
498
+ }}
499
+ >
500
+ {/* Background track */}
501
+ <div style={{
502
+ position: 'absolute', left: 0, right: 0,
503
+ height: 4, borderRadius: 2,
504
+ background: 'var(--rail-surface-2, #E3E1D7)',
505
+ pointerEvents: 'none',
506
+ }} />
507
+ {/* Played portion */}
508
+ <div style={{
509
+ position: 'absolute', left: 0, top: '50%',
510
+ transform: 'translateY(-50%)',
511
+ width: `${progress}%`,
512
+ height: 4, borderRadius: 2,
513
+ background: 'var(--Green-Primary, #00925F)',
514
+ pointerEvents: 'none',
515
+ }} />
516
+ {/* Scrubber handle */}
517
+ <svg
518
+ width="16" height="16" viewBox="0 0 16 16" fill="none"
519
+ xmlns="http://www.w3.org/2000/svg"
505
520
  style={{
506
- flex: 1, height: 16,
507
- position: 'relative',
508
- display: 'flex', alignItems: 'center',
509
- cursor: 'pointer',
521
+ position: 'absolute',
522
+ left: `${progress}%`,
523
+ top: '50%',
524
+ transform: 'translate(-50%, -50%)',
525
+ pointerEvents: 'none',
510
526
  }}
511
527
  >
512
- {/* Background track — Figma: #E3E1D7, stroke w=4 */}
513
- <div style={{
514
- position: 'absolute', left: 0, right: 0,
515
- height: 4, borderRadius: 2,
516
- background: 'var(--rail-surface-2, #E3E1D7)',
517
- pointerEvents: 'none',
518
- }} />
519
- {/* Played portion — Figma: #00925F, stroke w=4 */}
520
- <div style={{
521
- position: 'absolute', left: 0, top: '50%',
522
- transform: 'translateY(-50%)',
523
- width: `${progress}%`,
524
- height: 4, borderRadius: 2,
525
- background: 'var(--Green-Primary, #00925F)',
526
- pointerEvents: 'none',
527
- }} />
528
- {/* Scrubber handle — Figma ellipse */}
529
- <svg
530
- width="16" height="16" viewBox="0 0 16 16" fill="none"
531
- xmlns="http://www.w3.org/2000/svg"
532
- style={{
533
- position: 'absolute',
534
- left: `${progress}%`,
535
- top: '50%',
536
- transform: 'translate(-50%, -50%)',
537
- pointerEvents: 'none',
538
- }}
539
- >
540
- <path
541
- d="M7.875 1C11.6572 1 14.75 4.1191 14.75 8C14.75 11.8809 11.6572 15 7.875 15C4.09284 15 1 11.8809 1 8C1 4.1191 4.09284 1 7.875 1Z"
542
- fill="#FFF"
543
- stroke="#00925F"
544
- strokeWidth="2"
545
- />
546
- </svg>
547
- </div>
528
+ <path
529
+ d="M7.875 1C11.6572 1 14.75 4.1191 14.75 8C14.75 11.8809 11.6572 15 7.875 15C4.09284 15 1 11.8809 1 8C1 4.1191 4.09284 1 7.875 1Z"
530
+ fill="#FFF"
531
+ stroke="#00925F"
532
+ strokeWidth="2"
533
+ />
534
+ </svg>
548
535
  </div>
536
+ </div>
549
537
 
550
- {/* ── Row 3: Speaker timeline bars + playback indicator ── */}
551
- <div style={{ position: 'relative', paddingBottom: 30, cursor: 'pointer' }}>
552
- {['Agent', 'Customer'].map((speaker, rowIdx) => (
538
+ {/* ── Speaker timeline bars + playback indicator ── */}
539
+ <div style={{ position: 'relative', paddingBottom: 30, cursor: 'pointer' }}>
540
+ {[agentName, customerName].map((speaker, rowIdx) => {
541
+ const isAgent = rowIdx === 0;
542
+ // Build segments from real timelineSegments or fall back to demo
543
+ const speakerSegments = timelineSegments?.length
544
+ ? timelineSegments
545
+ .filter(s => isAgent ? s.actor === agentName : s.actor === customerName)
546
+ .map(s => ({
547
+ start: effectiveDuration > 0 ? s.startTime / effectiveDuration : 0,
548
+ end: effectiveDuration > 0 ? s.endTime / effectiveDuration : 0,
549
+ }))
550
+ : (DEMO_SEGMENTS[isAgent ? 'Agent' : 'Customer'] || []);
551
+
552
+ return (
553
553
  <div key={speaker} style={{
554
554
  display: 'flex', alignItems: 'center', gap: 8,
555
555
  height: 20,
@@ -576,7 +576,7 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
576
576
  cursor: 'pointer',
577
577
  }}
578
578
  >
579
- {/* Thin baseline — Figma: #E3E1D7, stroke w=4 */}
579
+ {/* Thin baseline */}
580
580
  <div style={{
581
581
  position: 'absolute', left: 0, right: 0,
582
582
  top: '50%', transform: 'translateY(-50%)',
@@ -584,68 +584,74 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
584
584
  background: 'var(--rail-surface-2, #E3E1D7)',
585
585
  }} />
586
586
  {/* Speech segments */}
587
- {DEMO_SEGMENTS[speaker].map((seg, i) => (
587
+ {speakerSegments.map((seg, i) => (
588
588
  <div key={i} style={{
589
589
  position: 'absolute',
590
590
  left: `${seg.start * 100}%`,
591
591
  width: `${(seg.end - seg.start) * 100}%`,
592
592
  top: 0, bottom: 0, borderRadius: 3,
593
- background: speaker === 'Agent'
593
+ background: isAgent
594
594
  ? 'var(--Grey-Strong, #2E3236)'
595
595
  : 'var(--Grey-Muted, #808183)',
596
596
  }} />
597
597
  ))}
598
598
  </div>
599
599
  </div>
600
- ))}
601
-
602
- {/* ── Playback position: dotted line + time tooltip ── */}
600
+ );
601
+ })}
602
+
603
+ {/* ── Playback position: dotted line + time tooltip ── */}
604
+ <div style={{
605
+ position: 'absolute',
606
+ left: 68, /* 60 label + 8 gap */
607
+ right: 0,
608
+ top: 0,
609
+ bottom: 0,
610
+ pointerEvents: 'none',
611
+ }}>
612
+ {/* Dotted vertical line */}
603
613
  <div style={{
604
614
  position: 'absolute',
605
- left: 68, /* 60 label + 8 gap */
606
- right: 0,
615
+ left: `${progress}%`,
607
616
  top: 0,
617
+ height: 44,
618
+ transform: 'translateX(-50%)',
619
+ borderLeft: '1.5px dashed var(--Grey-Muted, #808183)',
620
+ opacity: 0.5,
621
+ }} />
622
+
623
+ {/* Time tooltip */}
624
+ <div style={{
625
+ position: 'absolute',
626
+ left: `${progress}%`,
608
627
  bottom: 0,
609
- pointerEvents: 'none',
628
+ transform: 'translateX(-50%)',
629
+ background: 'var(--Grey-Strong, #2E3236)',
630
+ color: 'var(--Grey-White, #FFF)',
631
+ fontSize: 14, fontWeight: 600,
632
+ lineHeight: 1.2,
633
+ padding: '4px 6px',
634
+ borderRadius: 4,
635
+ whiteSpace: 'nowrap',
610
636
  }}>
611
- {/* Dotted vertical line */}
612
- <div style={{
613
- position: 'absolute',
614
- left: `${progress}%`,
615
- top: 0,
616
- height: 44, /* spans both rows */
617
- transform: 'translateX(-50%)',
618
- borderLeft: '1.5px dashed var(--Grey-Muted, #808183)',
619
- opacity: 0.5,
620
- }} />
621
-
622
- {/* Time tooltip — Figma: Frame 30, bg=#2E3236, pad 4 6, r=4, 14px/600/#FFF */}
623
- <div style={{
624
- position: 'absolute',
625
- left: `${progress}%`,
626
- bottom: 0,
627
- transform: 'translateX(-50%)',
628
- background: 'var(--Grey-Strong, #2E3236)',
629
- color: 'var(--Grey-White, #FFF)',
630
- fontSize: 14, fontWeight: 600,
631
- lineHeight: 1.2,
632
- padding: '4px 6px',
633
- borderRadius: 4,
634
- whiteSpace: 'nowrap',
635
- }}>
636
- {fmtTime(displayTime)}
637
- </div>
637
+ {fmtTime(displayTime)}
638
638
  </div>
639
639
  </div>
640
-
641
- {/* Hidden audio element for self-managed mode */}
642
- {selfManaged && (
643
- <audio ref={internalAudioRef} preload="auto" style={{ display: 'none' }}>
644
- <source src={audioUrl} type="audio/mpeg" />
645
- </audio>
646
- )}
647
640
  </div>
648
- )}
641
+
642
+ {/* Hidden audio element — only for self-managed mode */}
643
+ {selfManaged && (
644
+ <audio ref={internalAudioRef} preload="auto" style={{ display: 'none' }}>
645
+ <source src={audioUrl} type="audio/mpeg" />
646
+ </audio>
647
+ )}
648
+ {/* Hidden audio element — for parent-managed mode */}
649
+ {parentManaged && audioUrl && (
650
+ <audio ref={externalAudioRef} preload="auto" style={{ display: 'none' }}>
651
+ <source src={audioUrl} type="audio/mpeg" />
652
+ </audio>
653
+ )}
654
+ </div>
649
655
  </div>
650
656
 
651
657
  {/* ════════════════════════════════════════
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react';
2
- import { ChevronDown, ChevronUp, PlayCircle, FileSignal, ScrollText } from 'lucide-react';
2
+ import { ChevronDown, ChevronUp, PlayCircle, PauseCircle, FileSignal, ScrollText } from 'lucide-react';
3
3
 
4
4
  /* Hover circle wrapper for icons */
5
5
  const HoverIcon = ({ children, size = 28, onClick, title }) => {
@@ -285,11 +285,19 @@ const UpdatedInteractionSignals = ({
285
285
  }}
286
286
  >
287
287
  <HoverIcon size={28}>
288
- <PlayCircle
289
- size={17}
290
- color={isEvPlaying(ev) ? 'var(--rail-orange, #C98A5A)' : 'var(--Grey-Muted, #808183)'}
291
- strokeWidth={1}
292
- />
288
+ {isEvPlaying(ev) ? (
289
+ <PauseCircle
290
+ size={17}
291
+ color="var(--Grey-Muted, #808183)"
292
+ strokeWidth={1.5}
293
+ />
294
+ ) : (
295
+ <PlayCircle
296
+ size={17}
297
+ color="var(--Grey-Muted, #808183)"
298
+ strokeWidth={1}
299
+ />
300
+ )}
293
301
  </HoverIcon>
294
302
  <span style={{
295
303
  fontSize: 13,
@@ -830,10 +830,11 @@ export default function LoginPage({
830
830
 
831
831
  {/* ── RIGHT — Form Panel ── */}
832
832
  <div style={{
833
- flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', justifyContent: 'center',
834
- alignItems: 'center', padding: view === 'waitlist' ? '20px 48px' : '32px 72px', position: 'relative',
833
+ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column',
834
+ alignItems: 'center', padding: view === 'waitlist' ? '20px 48px' : '32px 72px 40px', position: 'relative',
835
835
  background: 'white',
836
836
  overflowY: 'auto', minHeight: 0,
837
+ justifyContent: 'center',
837
838
  }}>
838
839
  <div style={{
839
840
  display: 'flex',
@@ -1414,12 +1415,26 @@ export default function LoginPage({
1414
1415
  }}
1415
1416
  />
1416
1417
  {/* <NavRow text="Not a member yet?" linkText="Sign Up" onClick={() => { setFirstName(''); setLastName(''); setSignupEmail(''); setSignupPassword(''); setSignupConfirm(''); setSignupError(''); setView('signup'); onSignUp?.(); }} /> */}
1417
- <TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={16} />
1418
1418
  </div>
1419
1419
  </div>
1420
1420
  )}
1421
1421
 
1422
1422
  </div>
1423
+ {/* Terms footer pinned to bottom — only for sign-in view (other views have inline footer) */}
1424
+ {view === 'signin' && (
1425
+ <div style={{
1426
+ position: 'absolute',
1427
+ bottom: 40,
1428
+ left: 0,
1429
+ right: 0,
1430
+ display: 'flex',
1431
+ justifyContent: 'center',
1432
+ }}>
1433
+ <div style={{ width: '100%', maxWidth: 360 }}>
1434
+ <TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={0} />
1435
+ </div>
1436
+ </div>
1437
+ )}
1423
1438
  </div>
1424
1439
  </div>
1425
1440
  );
@@ -1,7 +0,0 @@
1
- "use strict";const r=require("react/jsx-runtime"),l=require("react"),T=require("lucide-react"),w={customer:{label:"Customer",railColor:"var(--rail-discovery)",cardBg:"var(--card-customer)",cardBgHighlight:"rgba(94, 136, 176, 0.12)",borderColor:"var(--border-subtle)"},agent:{label:"Agent",railColor:"var(--rail-outcome)",cardBg:"var(--card-agent)",cardBgHighlight:"rgba(107, 124, 147, 0.10)",borderColor:"var(--border-subtle)"},assistant:{label:"AI Assistant",railColor:"var(--rail-purple)",cardBg:"var(--card-assistant)",cardBgHighlight:"rgba(155, 122, 168, 0.10)",borderColor:"var(--border-subtle)"},system:{label:"System",railColor:"var(--text-faint)",cardBg:"transparent",cardBgHighlight:"transparent",borderColor:"transparent"}},E="conversation-turn-keyframes";function L(){if(typeof document>"u"||document.getElementById(E))return;const i=document.createElement("style");i.id=E,i.textContent=`
2
- @keyframes turn-playing-pulse {
3
- 0%, 100% { opacity: 0.4; }
4
- 50% { opacity: 0.9; }
5
- }
6
- `,document.head.appendChild(i)}function A({role:i="agent",text:s,actorLabel:c,actorRailColor:h,actionKicker:x,toolBadges:f,streaming:d=!1,meta:v,timeRange:b,isHighlighted:m=!1,highlightRailColor:t,observations:n,onObservationClick:y,children:k}){l.useEffect(()=>{L()},[]);const p=w[i]||w.agent,j=c||p.label,C=h||p.railColor,B=m||d;return i==="system"?r.jsx("div",{style:{textAlign:"center",padding:"6px 0",flexShrink:0,fontSize:"var(--text-sm, 11px)",color:"var(--text-faint, rgba(30,33,37,0.36))",fontWeight:500,letterSpacing:"0.02em"},children:s}):r.jsxs("div",{style:{position:"relative",padding:"10px 14px 10px 18px",borderRadius:10,overflow:"hidden",flexShrink:0,background:B?p.cardBgHighlight:p.cardBg,border:`1px solid ${d?C:p.borderColor}`,boxShadow:d?`0 0 8px ${p.cardBgHighlight}`:"none",transition:"background 0.2s, border-color 0.2s, box-shadow 0.2s"},children:[r.jsx("div",{style:{position:"absolute",left:0,top:0,bottom:0,width:"var(--rail-width-thin, 4px)",backgroundColor:C,borderRadius:"10px 0 0 10px"}}),r.jsxs("div",{style:{display:"flex",alignItems:"flex-start",justifyContent:"space-between",gap:8,marginBottom:4},children:[r.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[x?r.jsx("div",{style:{fontSize:"var(--text-xs-plus, 10.5px)",fontWeight:650,textTransform:"uppercase",letterSpacing:"var(--tracking-label, 0.16em)",color:C},children:x}):null,r.jsx("div",{style:{fontSize:"var(--text-xs-plus, 10.5px)",fontWeight:650,textTransform:"uppercase",letterSpacing:"var(--tracking-label, 0.16em)",color:"var(--text-faint, rgba(30,33,37,0.36))"},children:j})]}),f&&f.length>0?r.jsx("div",{style:{display:"flex",flexWrap:"wrap",gap:4,flexShrink:0,justifyContent:"flex-end"},children:f.map((e,a)=>{const o=e.pending,g=!o&&e.success===!1,u=o?"var(--rail-discovery, #5E88B0)":g?"var(--rail-compliance, #C98A5A)":"var(--text-xfaint, rgba(30,33,37,0.28))";return r.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:5,fontSize:"var(--text-xs, 10px)",padding:"2px 8px 2px 6px",borderRadius:999,background:"var(--paper, rgba(255,255,255,0.78))",border:"1px solid var(--border-subtle, rgba(52,58,64,0.08))",color:"var(--text-faint, rgba(30,33,37,0.36))",fontWeight:500,fontFamily:"var(--font-fira-code, var(--font-mono, monospace))",letterSpacing:"0.01em",lineHeight:1.4,whiteSpace:"nowrap"},children:[r.jsx("span",{style:{width:5,height:5,borderRadius:"50%",backgroundColor:u,flexShrink:0}}),e.name]},a)})}):null]}),r.jsxs("div",{style:{fontSize:"var(--text-md, 13px)",lineHeight:"var(--leading-normal, 1.5)",color:d?"var(--text-strong, rgba(30,33,37,0.92))":"var(--text-base, rgba(30,33,37,0.78))",fontWeight:d?550:400},children:[s,d?r.jsx("span",{style:{display:"inline-block",width:6,height:14,background:C,marginLeft:2,borderRadius:1,animation:"cursorBlink 0.8s ease-in-out infinite",verticalAlign:"text-bottom"}}):null]}),b||k||n&&n.length>0?r.jsxs("div",{style:{marginTop:6,display:"flex",alignItems:"center",gap:6},children:[k,b?r.jsx("span",{style:{fontSize:"var(--text-sm, 11px)",padding:"2px 8px",borderRadius:999,border:"1px solid var(--border, rgba(52,58,64,0.12))",background:"var(--timestamp-bg, rgba(255,255,255,0.70))",color:"var(--text-muted, rgba(30,33,37,0.56))",fontFamily:"var(--font-mono, monospace)"},children:b}):null,n&&n.length>0?r.jsx("div",{style:{display:"flex",flexWrap:"wrap",gap:4,marginLeft:"auto"},children:n.map((e,a)=>r.jsxs("span",{onClick:e.onClick||(y?()=>y(e):void 0),style:{display:"inline-flex",alignItems:"center",gap:4,fontSize:"var(--text-xs, 10px)",padding:"2px 8px",borderRadius:999,background:`color-mix(in srgb, ${e.color||"var(--state-present)"} 12%, transparent)`,border:`1px solid color-mix(in srgb, ${e.color||"var(--state-present)"} 25%, transparent)`,color:e.color||"var(--state-present)",fontWeight:550,lineHeight:1.4,whiteSpace:"nowrap",cursor:e.onClick||y?"pointer":"default",transition:"background 0.15s"},title:e.reason||e.label,children:[r.jsx("span",{style:{width:4,height:4,borderRadius:"50%",backgroundColor:e.color||"var(--state-present)",flexShrink:0,opacity:.7}}),e.label]},a))}):null]}):null,v?r.jsx("div",{style:{marginTop:6,fontSize:"var(--text-xs-plus, 10.5px)",color:"var(--text-faint, rgba(30,33,37,0.36))",fontFamily:"var(--font-mono, monospace)"},children:v}):null,B&&t?r.jsx("div",{style:{position:"absolute",right:8,top:8,bottom:8,width:6,borderRadius:4,backgroundColor:t,opacity:.7,animation:"turn-playing-pulse 1.5s ease-in-out infinite"}}):null]})}const P=i=>{const[s,c]=i.split("–").map(h=>{const[x,f]=h.split(":").map(Number);return x*60+f});return{start:s,end:c}},W=i=>({customer:"customer",agent:"agent",third_party:"agent",system:"system",assistant:"assistant"})[i]||"agent";function z({turns:i,audioUrl:s,activeTurnIndex:c=-1,autoScrollActiveTurn:h=!1,isExternalPlaying:x=!1,onTurnPlayPause:f}){const[d,v]=l.useState(null),[b,m]=l.useState(!1),t=l.useRef(null),n=l.useRef(null),y=l.useRef([]),k=l.useRef(null),p=l.useRef(-1),j=typeof f=="function",C=x!==void 0;l.useEffect(()=>{if(s)return t.current=new Audio(s),t.current.preload="auto",()=>{t.current&&(t.current.pause(),t.current=null)}},[s]),l.useEffect(()=>()=>{t.current&&(t.current.pause(),n.current&&t.current.removeEventListener("timeupdate",n.current))},[]),l.useEffect(()=>{if(!h||c<0)return;const e=k.current,a=y.current[c];if(!a||!e)return;const o=e.getBoundingClientRect(),g=a.getBoundingClientRect(),u=12,R=g.top>=o.top+u&&g.bottom<=o.bottom-8;if(!(p.current===c)||!R){const H=g.top-o.top+e.scrollTop;e.scrollTop=Math.max(H-u,0)}p.current=c},[c,h]);const B=(e,a)=>{if(j){f(i[a],a);return}if(d===a&&b)t.current&&(t.current.pause(),n.current&&(t.current.removeEventListener("timeupdate",n.current),n.current=null)),m(!1);else{const o=d;if(v(a),s&&t.current){const{start:g,end:u}=P(e);o!==null&&o!==a&&(t.current.pause(),n.current&&(t.current.removeEventListener("timeupdate",n.current),n.current=null));const R=()=>{if(t.current){if(t.current.currentTime=g,u){const S=()=>{t.current&&t.current.currentTime>=u&&(t.current.pause(),m(!1),v(null),n.current&&(t.current.removeEventListener("timeupdate",n.current),n.current=null))};n.current=S,t.current.addEventListener("timeupdate",S)}t.current.play().then(()=>m(!0)).catch(S=>{S&&S.name==="AbortError"||(m(!1),v(null))})}};t.current.readyState>=2?R():(t.current.addEventListener("loadedmetadata",R,{once:!0}),t.current.load())}else m(!0)}};return r.jsxs("div",{ref:k,className:"custom-thin-scrollbar-library",style:{maxHeight:560,overflowY:"auto",scrollBehavior:"smooth",border:"1px solid var(--border, rgba(52,58,64,0.12))",borderRadius:"12px 0px 0px 12px",background:"var(--paper-elevated, rgba(255,255,255,0.82))",padding:16},children:[r.jsx("div",{style:{fontSize:"var(--text-xs-plus, 10.5px)",fontWeight:650,textTransform:"uppercase",letterSpacing:"var(--tracking-label, 0.16em)",color:"var(--text-faint, rgba(30,33,37,0.36))",marginBottom:12},children:"Transcript"}),r.jsx("div",{style:{display:"flex",flexDirection:"column",gap:8},children:i.map((e,a)=>{const o=j||C?!!x&&c===a:d===a&&b,g=!!e.isHighlighted||o,u=W(e.actorType);return r.jsx("div",{ref:R=>{y.current[a]=R},children:r.jsx(A,{role:u,text:e.text,actorLabel:e.actor,actorRailColor:e.actorColor,timeRange:e.timeRange,isHighlighted:g,highlightRailColor:o?e.highlightColor||(u==="agent"?"var(--rail-outcome)":"var(--rail-discovery)"):e.highlightColor,toolBadges:e.toolBadges,observations:e.observations,onObservationClick:e.onObservationClick,children:e.timeRange?r.jsx("button",{onClick:()=>B(e.timeRange,a),style:{width:24,height:24,borderRadius:"50%",border:"1px solid var(--border, rgba(52,58,64,0.12))",background:o?"var(--rail-discovery, #5E88B0)":"var(--paper, rgba(255,255,255,0.78))",display:"inline-flex",alignItems:"center",justifyContent:"center",cursor:"pointer",padding:0,transition:"all 0.15s"},"aria-label":o?`Pause segment ${e.timeRange}`:`Play segment ${e.timeRange}`,children:o?r.jsx(T.Pause,{style:{width:12,height:12,color:"var(--paper, #fff)",fill:"var(--paper, #fff)"},strokeWidth:0}):r.jsx(T.Play,{style:{width:12,height:12,color:"var(--text-muted, rgba(30,33,37,0.56))",fill:"var(--text-muted, rgba(30,33,37,0.56))",marginLeft:1},strokeWidth:0})}):null})},a)})})]})}exports.ConversationTurn=A;exports.TranscriptCard=z;
7
- //# sourceMappingURL=TranscriptCard.cjs.js.map