chordia-ui 3.4.2 → 3.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Timeline.cjs.js +6 -1
- package/dist/Timeline.cjs.js.map +1 -1
- package/dist/Timeline.es.js +597 -129
- package/dist/Timeline.es.js.map +1 -1
- package/dist/UpdatedInteractionRecording.cjs.js +1 -1
- package/dist/UpdatedInteractionRecording.cjs.js.map +1 -1
- package/dist/UpdatedInteractionRecording.es.js +260 -258
- package/dist/UpdatedInteractionRecording.es.js.map +1 -1
- package/dist/components/UpdatedInteractionDetails.cjs.js +2 -2
- package/dist/components/UpdatedInteractionDetails.cjs.js.map +1 -1
- package/dist/components/UpdatedInteractionDetails.es.js +349 -322
- package/dist/components/UpdatedInteractionDetails.es.js.map +1 -1
- package/dist/components/media.cjs.js +1 -1
- package/dist/components/media.cjs.js.map +1 -1
- package/dist/components/media.es.js +8 -9
- package/dist/components/media.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +52 -53
- package/dist/index.es.js.map +1 -1
- package/dist/pages/interactionDetails.cjs.js +2 -2
- package/dist/pages/interactionDetails.cjs.js.map +1 -1
- package/dist/pages/interactionDetails.es.js +16 -17
- package/dist/pages/interactionDetails.es.js.map +1 -1
- package/package.json +1 -1
- package/src/components/UpdatedInteractionDetails/UpdatedInteractionDetails.jsx +35 -1
- package/src/components/UpdatedInteractionDetails/UpdatedInteractionRecording.jsx +142 -124
- package/src/components/UpdatedInteractionDetails/UpdatedInteractionSignals.jsx +14 -6
- package/dist/TranscriptCard.cjs.js +0 -7
- package/dist/TranscriptCard.cjs.js.map +0 -1
- package/dist/TranscriptCard.es.js +0 -474
- package/dist/TranscriptCard.es.js.map +0 -1
package/package.json
CHANGED
|
@@ -43,6 +43,9 @@ 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,
|
|
46
49
|
// Footer navigation
|
|
47
50
|
prevSessionTitle,
|
|
48
51
|
prevSessionDesc,
|
|
@@ -55,6 +58,8 @@ const UpdatedInteractionDetails = ({
|
|
|
55
58
|
const [expandedSignals, setExpandedSignals] = useState(new Set());
|
|
56
59
|
const [showSessionDropdown, setShowSessionDropdown] = useState(false);
|
|
57
60
|
const [hoveredSessionIdx, setHoveredSessionIdx] = useState(null);
|
|
61
|
+
const [internalPlaying, setInternalPlaying] = useState(false);
|
|
62
|
+
const [internalCurrentTime, setInternalCurrentTime] = useState(0);
|
|
58
63
|
const recordingRef = useRef(null);
|
|
59
64
|
|
|
60
65
|
// Section refs for scroll-to-section tab behavior
|
|
@@ -83,12 +88,37 @@ const UpdatedInteractionDetails = ({
|
|
|
83
88
|
/* Called from Signals "Show in transcript" icon — seeks recording to that time */
|
|
84
89
|
const handleShowInTranscript = (startMs) => {
|
|
85
90
|
const timeSec = startMs / 1000;
|
|
86
|
-
// Call the recording component's seek via ref
|
|
87
91
|
if (recordingRef.current?.seekTo) {
|
|
88
92
|
recordingRef.current.seekTo(timeSec);
|
|
89
93
|
}
|
|
90
94
|
};
|
|
91
95
|
|
|
96
|
+
/* Called from Signals evidence play button — seeks + plays audio segment */
|
|
97
|
+
const handlePlayEvidence = (ev) => {
|
|
98
|
+
if (onPlayEvidence) {
|
|
99
|
+
onPlayEvidence(ev);
|
|
100
|
+
} else {
|
|
101
|
+
// Default: seek to evidence start time via recording ref
|
|
102
|
+
const startMs = ev.start_ms ?? ev.startMs;
|
|
103
|
+
if (startMs != null && recordingRef.current?.seekTo) {
|
|
104
|
+
recordingRef.current.seekTo(startMs / 1000);
|
|
105
|
+
setInternalPlaying(true);
|
|
106
|
+
setInternalCurrentTime(startMs / 1000);
|
|
107
|
+
// Stop playing after evidence duration
|
|
108
|
+
const endMs = ev.end_ms ?? ev.endMs ?? (startMs + 5000);
|
|
109
|
+
const durationMs = endMs - startMs;
|
|
110
|
+
setTimeout(() => setInternalPlaying(false), durationMs);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/* Called to highlight transcript turns related to evidence */
|
|
116
|
+
const handleHighlightTurns = (turnIds) => {
|
|
117
|
+
if (onHighlightTurns) {
|
|
118
|
+
onHighlightTurns(turnIds);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
92
122
|
const toggleSignal = (key) => {
|
|
93
123
|
setExpandedSignals((prev) => {
|
|
94
124
|
const next = new Set(prev);
|
|
@@ -526,7 +556,11 @@ const UpdatedInteractionDetails = ({
|
|
|
526
556
|
signals={demoSignals}
|
|
527
557
|
expandedSignals={expandedSignals}
|
|
528
558
|
toggleSignal={toggleSignal}
|
|
559
|
+
playEvidence={handlePlayEvidence}
|
|
560
|
+
highlightTurns={handleHighlightTurns}
|
|
529
561
|
onShowInTranscript={handleShowInTranscript}
|
|
562
|
+
timelinePlaying={timelinePlaying || internalPlaying}
|
|
563
|
+
currentTimeSeconds={currentTimeSeconds ?? internalCurrentTime}
|
|
530
564
|
/>
|
|
531
565
|
</div>
|
|
532
566
|
<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 (
|
|
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,98 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
|
|
|
465
470
|
</div>
|
|
466
471
|
</div>
|
|
467
472
|
|
|
468
|
-
{/* ── Row 2: Progress bar ── */}
|
|
469
|
-
{
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
<
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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: 600,
|
|
479
|
+
color: progress > 0 ? 'var(--Green-Primary, #00925F)' : 'var(--Grey-Strong, #2E3236)',
|
|
480
|
+
fontFamily: 'var(--font-sans)',
|
|
481
|
+
lineHeight: 1.2,
|
|
482
|
+
minWidth: 60,
|
|
483
|
+
}}>
|
|
484
|
+
{fmtTime(displayTime)}
|
|
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"
|
|
520
|
+
style={{
|
|
521
|
+
position: 'absolute',
|
|
522
|
+
left: `${progress}%`,
|
|
523
|
+
top: '50%',
|
|
524
|
+
transform: 'translate(-50%, -50%)',
|
|
525
|
+
pointerEvents: 'none',
|
|
526
|
+
}}
|
|
527
|
+
>
|
|
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>
|
|
535
|
+
{/* Duration at end — positioned inside the track so it doesn't affect track width */}
|
|
489
536
|
<span style={{
|
|
490
|
-
|
|
491
|
-
|
|
537
|
+
position: 'absolute',
|
|
538
|
+
right: -48,
|
|
539
|
+
fontSize: 13, fontWeight: 400,
|
|
540
|
+
color: 'var(--Grey-Muted, #808183)',
|
|
492
541
|
fontFamily: 'var(--font-sans)',
|
|
493
542
|
lineHeight: 1.2,
|
|
494
|
-
|
|
543
|
+
whiteSpace: 'nowrap',
|
|
495
544
|
}}>
|
|
496
|
-
{fmtTime(
|
|
545
|
+
{fmtTime(effectiveDuration)}
|
|
497
546
|
</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
|
-
}}
|
|
505
|
-
style={{
|
|
506
|
-
flex: 1, height: 16,
|
|
507
|
-
position: 'relative',
|
|
508
|
-
display: 'flex', alignItems: 'center',
|
|
509
|
-
cursor: 'pointer',
|
|
510
|
-
}}
|
|
511
|
-
>
|
|
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>
|
|
548
547
|
</div>
|
|
548
|
+
</div>
|
|
549
549
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
550
|
+
{/* ── Speaker timeline bars + playback indicator ── */}
|
|
551
|
+
<div style={{ position: 'relative', paddingBottom: 30, cursor: 'pointer' }}>
|
|
552
|
+
{[agentName, customerName].map((speaker, rowIdx) => {
|
|
553
|
+
const isAgent = rowIdx === 0;
|
|
554
|
+
// Build segments from real timelineSegments or fall back to demo
|
|
555
|
+
const speakerSegments = timelineSegments?.length
|
|
556
|
+
? timelineSegments
|
|
557
|
+
.filter(s => isAgent ? s.actor === agentName : s.actor === customerName)
|
|
558
|
+
.map(s => ({
|
|
559
|
+
start: effectiveDuration > 0 ? s.startTime / effectiveDuration : 0,
|
|
560
|
+
end: effectiveDuration > 0 ? s.endTime / effectiveDuration : 0,
|
|
561
|
+
}))
|
|
562
|
+
: (DEMO_SEGMENTS[isAgent ? 'Agent' : 'Customer'] || []);
|
|
563
|
+
|
|
564
|
+
return (
|
|
553
565
|
<div key={speaker} style={{
|
|
554
566
|
display: 'flex', alignItems: 'center', gap: 8,
|
|
555
567
|
height: 20,
|
|
@@ -576,7 +588,7 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
|
|
|
576
588
|
cursor: 'pointer',
|
|
577
589
|
}}
|
|
578
590
|
>
|
|
579
|
-
{/* Thin baseline
|
|
591
|
+
{/* Thin baseline */}
|
|
580
592
|
<div style={{
|
|
581
593
|
position: 'absolute', left: 0, right: 0,
|
|
582
594
|
top: '50%', transform: 'translateY(-50%)',
|
|
@@ -584,68 +596,74 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
|
|
|
584
596
|
background: 'var(--rail-surface-2, #E3E1D7)',
|
|
585
597
|
}} />
|
|
586
598
|
{/* Speech segments */}
|
|
587
|
-
{
|
|
599
|
+
{speakerSegments.map((seg, i) => (
|
|
588
600
|
<div key={i} style={{
|
|
589
601
|
position: 'absolute',
|
|
590
602
|
left: `${seg.start * 100}%`,
|
|
591
603
|
width: `${(seg.end - seg.start) * 100}%`,
|
|
592
604
|
top: 0, bottom: 0, borderRadius: 3,
|
|
593
|
-
background:
|
|
605
|
+
background: isAgent
|
|
594
606
|
? 'var(--Grey-Strong, #2E3236)'
|
|
595
607
|
: 'var(--Grey-Muted, #808183)',
|
|
596
608
|
}} />
|
|
597
609
|
))}
|
|
598
610
|
</div>
|
|
599
611
|
</div>
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
|
|
612
|
+
);
|
|
613
|
+
})}
|
|
614
|
+
|
|
615
|
+
{/* ── Playback position: dotted line + time tooltip ── */}
|
|
616
|
+
<div style={{
|
|
617
|
+
position: 'absolute',
|
|
618
|
+
left: 68, /* 60 label + 8 gap */
|
|
619
|
+
right: 0,
|
|
620
|
+
top: 0,
|
|
621
|
+
bottom: 0,
|
|
622
|
+
pointerEvents: 'none',
|
|
623
|
+
}}>
|
|
624
|
+
{/* Dotted vertical line */}
|
|
603
625
|
<div style={{
|
|
604
626
|
position: 'absolute',
|
|
605
|
-
left:
|
|
606
|
-
right: 0,
|
|
627
|
+
left: `${progress}%`,
|
|
607
628
|
top: 0,
|
|
629
|
+
height: 44,
|
|
630
|
+
transform: 'translateX(-50%)',
|
|
631
|
+
borderLeft: '1.5px dashed var(--Grey-Muted, #808183)',
|
|
632
|
+
opacity: 0.5,
|
|
633
|
+
}} />
|
|
634
|
+
|
|
635
|
+
{/* Time tooltip */}
|
|
636
|
+
<div style={{
|
|
637
|
+
position: 'absolute',
|
|
638
|
+
left: `${progress}%`,
|
|
608
639
|
bottom: 0,
|
|
609
|
-
|
|
640
|
+
transform: 'translateX(-50%)',
|
|
641
|
+
background: 'var(--Grey-Strong, #2E3236)',
|
|
642
|
+
color: 'var(--Grey-White, #FFF)',
|
|
643
|
+
fontSize: 14, fontWeight: 600,
|
|
644
|
+
lineHeight: 1.2,
|
|
645
|
+
padding: '4px 6px',
|
|
646
|
+
borderRadius: 4,
|
|
647
|
+
whiteSpace: 'nowrap',
|
|
610
648
|
}}>
|
|
611
|
-
{
|
|
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>
|
|
649
|
+
{fmtTime(displayTime)}
|
|
638
650
|
</div>
|
|
639
651
|
</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
652
|
</div>
|
|
648
|
-
|
|
653
|
+
|
|
654
|
+
{/* Hidden audio element — only for self-managed mode */}
|
|
655
|
+
{selfManaged && (
|
|
656
|
+
<audio ref={internalAudioRef} preload="auto" style={{ display: 'none' }}>
|
|
657
|
+
<source src={audioUrl} type="audio/mpeg" />
|
|
658
|
+
</audio>
|
|
659
|
+
)}
|
|
660
|
+
{/* Hidden audio element — for parent-managed mode */}
|
|
661
|
+
{parentManaged && audioUrl && (
|
|
662
|
+
<audio ref={externalAudioRef} preload="auto" style={{ display: 'none' }}>
|
|
663
|
+
<source src={audioUrl} type="audio/mpeg" />
|
|
664
|
+
</audio>
|
|
665
|
+
)}
|
|
666
|
+
</div>
|
|
649
667
|
</div>
|
|
650
668
|
|
|
651
669
|
{/* ════════════════════════════════════════
|
|
@@ -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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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,
|
|
@@ -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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TranscriptCard.cjs.js","sources":["../src/components/media/ConversationTurn.jsx","../src/components/media/TranscriptCard.jsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect } from \"react\";\n\n/**\n * Actor configuration — rail color + subtle card tint per role.\n * Colors reference CSS custom properties from the Chordia design system.\n */\nconst ACTOR_DEFAULTS = {\n customer: {\n label: \"Customer\",\n railColor: \"var(--rail-discovery)\",\n cardBg: \"var(--card-customer)\",\n cardBgHighlight: \"rgba(94, 136, 176, 0.12)\",\n borderColor: \"var(--border-subtle)\",\n },\n agent: {\n label: \"Agent\",\n railColor: \"var(--rail-outcome)\",\n cardBg: \"var(--card-agent)\",\n cardBgHighlight: \"rgba(107, 124, 147, 0.10)\",\n borderColor: \"var(--border-subtle)\",\n },\n assistant: {\n label: \"AI Assistant\",\n railColor: \"var(--rail-purple)\",\n cardBg: \"var(--card-assistant)\",\n cardBgHighlight: \"rgba(155, 122, 168, 0.10)\",\n borderColor: \"var(--border-subtle)\",\n },\n system: {\n label: \"System\",\n railColor: \"var(--text-faint)\",\n cardBg: \"transparent\",\n cardBgHighlight: \"transparent\",\n borderColor: \"transparent\",\n },\n};\n\n/**\n * ConversationTurn Component\n * Displays a single turn in a live conversation stream.\n * Uses the TranscriptCard design language: left actor rail, uppercase label,\n * subtle per-actor card tinting.\n *\n * @param {Object} props\n * @param {string} props.role - \"customer\" | \"agent\" | \"assistant\" | \"system\"\n * @param {string} props.text - Message content\n * @param {string} [props.actorLabel] - Override the default actor label\n * @param {string} [props.actorRailColor] - Override the rail color for this turn\n * @param {string} [props.actionKicker] - NBA action label above message\n * @param {Array} [props.toolBadges] - [{name, success, pending}] tool call results\n * @param {boolean} [props.streaming] - Whether this message is actively streaming\n * @param {string} [props.meta] - Timing info, turn number, etc.\n * @param {string} [props.timeRange] - Time range label (e.g., \"00:03–00:07\")\n * @param {boolean} [props.isHighlighted] - Extra emphasis on this turn\n * @param {string} [props.highlightRailColor] - Color for the right highlight rail (when highlighted)\n * @param {React.ReactNode} [props.children] - Additional content below the message text\n */\nconst KEYFRAMES_ID = \"conversation-turn-keyframes\";\nfunction ensureKeyframes() {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(KEYFRAMES_ID)) return;\n const style = document.createElement(\"style\");\n style.id = KEYFRAMES_ID;\n style.textContent = `\n @keyframes turn-playing-pulse {\n 0%, 100% { opacity: 0.4; }\n 50% { opacity: 0.9; }\n }\n `;\n document.head.appendChild(style);\n}\n\nexport default function ConversationTurn({\n role = \"agent\",\n text,\n actorLabel,\n actorRailColor,\n actionKicker,\n toolBadges,\n streaming = false,\n meta,\n timeRange,\n isHighlighted = false,\n highlightRailColor,\n observations,\n onObservationClick,\n children,\n}) {\n useEffect(() => { ensureKeyframes(); }, []);\n const actor = ACTOR_DEFAULTS[role] || ACTOR_DEFAULTS.agent;\n const label = actorLabel || actor.label;\n const railColor = actorRailColor || actor.railColor;\n const highlighted = isHighlighted || streaming;\n\n // System messages: minimal centered text, no card\n if (role === \"system\") {\n return (\n <div\n style={{\n textAlign: \"center\",\n padding: \"6px 0\",\n flexShrink: 0,\n fontSize: \"var(--text-sm, 11px)\",\n color: \"var(--text-faint, rgba(30,33,37,0.36))\",\n fontWeight: 500,\n letterSpacing: \"0.02em\",\n }}\n >\n {text}\n </div>\n );\n }\n\n return (\n <div\n style={{\n position: \"relative\",\n padding: \"10px 14px 10px 18px\",\n borderRadius: 10,\n overflow: \"hidden\",\n flexShrink: 0,\n background: highlighted ? actor.cardBgHighlight : actor.cardBg,\n border: `1px solid ${streaming ? railColor : actor.borderColor}`,\n boxShadow: streaming\n ? `0 0 8px ${actor.cardBgHighlight}`\n : \"none\",\n transition: \"background 0.2s, border-color 0.2s, box-shadow 0.2s\",\n }}\n >\n {/* Left actor rail */}\n <div\n style={{\n position: \"absolute\",\n left: 0,\n top: 0,\n bottom: 0,\n width: \"var(--rail-width-thin, 4px)\",\n backgroundColor: railColor,\n borderRadius: \"10px 0 0 10px\",\n }}\n />\n\n {/* Header row: actor label left, tool badges right */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"flex-start\",\n justifyContent: \"space-between\",\n gap: 8,\n marginBottom: 4,\n }}\n >\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2 }}>\n {/* Action kicker (NBA label) */}\n {actionKicker ? (\n <div\n style={{\n fontSize: \"var(--text-xs-plus, 10.5px)\",\n fontWeight: 650,\n textTransform: \"uppercase\",\n letterSpacing: \"var(--tracking-label, 0.16em)\",\n color: railColor,\n }}\n >\n {actionKicker}\n </div>\n ) : null}\n\n {/* Actor label */}\n <div\n style={{\n fontSize: \"var(--text-xs-plus, 10.5px)\",\n fontWeight: 650,\n textTransform: \"uppercase\",\n letterSpacing: \"var(--tracking-label, 0.16em)\",\n color: \"var(--text-faint, rgba(30,33,37,0.36))\",\n }}\n >\n {label}\n </div>\n </div>\n\n {/* Tool badges — top right pills, uniform neutral with status dot */}\n {toolBadges && toolBadges.length > 0 ? (\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 4, flexShrink: 0, justifyContent: \"flex-end\" }}>\n {toolBadges.map((t, i) => {\n const isPending = t.pending;\n const isError = !isPending && t.success === false;\n // Dot color only — rail colors for categorization, not judgment\n const dotColor = isPending\n ? \"var(--rail-discovery, #5E88B0)\"\n : isError\n ? \"var(--rail-compliance, #C98A5A)\"\n : \"var(--text-xfaint, rgba(30,33,37,0.28))\";\n\n return (\n <span\n key={i}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 5,\n fontSize: \"var(--text-xs, 10px)\",\n padding: \"2px 8px 2px 6px\",\n borderRadius: 999,\n background: \"var(--paper, rgba(255,255,255,0.78))\",\n border: \"1px solid var(--border-subtle, rgba(52,58,64,0.08))\",\n color: \"var(--text-faint, rgba(30,33,37,0.36))\",\n fontWeight: 500,\n fontFamily: \"var(--font-fira-code, var(--font-mono, monospace))\",\n letterSpacing: \"0.01em\",\n lineHeight: 1.4,\n whiteSpace: \"nowrap\",\n }}\n >\n <span\n style={{\n width: 5,\n height: 5,\n borderRadius: \"50%\",\n backgroundColor: dotColor,\n flexShrink: 0,\n }}\n />\n {t.name}\n </span>\n );\n })}\n </div>\n ) : null}\n </div>\n\n {/* Message text */}\n <div\n style={{\n fontSize: \"var(--text-md, 13px)\",\n lineHeight: \"var(--leading-normal, 1.5)\",\n color: streaming\n ? \"var(--text-strong, rgba(30,33,37,0.92))\"\n : \"var(--text-base, rgba(30,33,37,0.78))\",\n fontWeight: streaming ? 550 : 400,\n }}\n >\n {text}\n {streaming ? (\n <span\n style={{\n display: \"inline-block\",\n width: 6,\n height: 14,\n background: railColor,\n marginLeft: 2,\n borderRadius: 1,\n animation: \"cursorBlink 0.8s ease-in-out infinite\",\n verticalAlign: \"text-bottom\",\n }}\n />\n ) : null}\n </div>\n\n {/* Time range + play button + observation pills — single row */}\n {timeRange || children || (observations && observations.length > 0) ? (\n <div style={{ marginTop: 6, display: \"flex\", alignItems: \"center\", gap: 6 }}>\n {children}\n {timeRange ? (\n <span\n style={{\n fontSize: \"var(--text-sm, 11px)\",\n padding: \"2px 8px\",\n borderRadius: 999,\n border: \"1px solid var(--border, rgba(52,58,64,0.12))\",\n background: \"var(--timestamp-bg, rgba(255,255,255,0.70))\",\n color: \"var(--text-muted, rgba(30,33,37,0.56))\",\n fontFamily: \"var(--font-mono, monospace)\",\n }}\n >\n {timeRange}\n </span>\n ) : null}\n {observations && observations.length > 0 ? (\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 4, marginLeft: \"auto\" }}>\n {observations.map((obs, i) => (\n <span\n key={i}\n onClick={obs.onClick || (onObservationClick ? () => onObservationClick(obs) : undefined)}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 4,\n fontSize: \"var(--text-xs, 10px)\",\n padding: \"2px 8px\",\n borderRadius: 999,\n background: `color-mix(in srgb, ${obs.color || \"var(--state-present)\"} 12%, transparent)`,\n border: `1px solid color-mix(in srgb, ${obs.color || \"var(--state-present)\"} 25%, transparent)`,\n color: obs.color || \"var(--state-present)\",\n fontWeight: 550,\n lineHeight: 1.4,\n whiteSpace: \"nowrap\",\n cursor: obs.onClick || onObservationClick ? \"pointer\" : \"default\",\n transition: \"background 0.15s\",\n }}\n title={obs.reason || obs.label}\n >\n <span style={{\n width: 4, height: 4, borderRadius: \"50%\",\n backgroundColor: obs.color || \"var(--state-present)\",\n flexShrink: 0, opacity: 0.7,\n }} />\n {obs.label}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {/* Meta / timestamp */}\n {meta ? (\n <div\n style={{\n marginTop: 6,\n fontSize: \"var(--text-xs-plus, 10.5px)\",\n color: \"var(--text-faint, rgba(30,33,37,0.36))\",\n fontFamily: \"var(--font-mono, monospace)\",\n }}\n >\n {meta}\n </div>\n ) : null}\n\n {/* Right highlight rail (used by TranscriptCard for active/highlighted turns) */}\n {highlighted && highlightRailColor ? (\n <div\n style={{\n position: \"absolute\",\n right: 8,\n top: 8,\n bottom: 8,\n width: 6,\n borderRadius: 4,\n backgroundColor: highlightRailColor,\n opacity: 0.7,\n animation: \"turn-playing-pulse 1.5s ease-in-out infinite\",\n }}\n />\n ) : null}\n </div>\n );\n}\n","\"use client\";\n\nimport React, { useState, useRef, useEffect } from \"react\";\nimport { Play, Pause } from \"lucide-react\";\nimport ConversationTurn from \"./ConversationTurn\";\n\n/**\n * Helper function to parse time range string (e.g., \"00:03–00:07\") to seconds\n */\nconst parseTimeRange = (timeRange) => {\n const [start, end] = timeRange.split(\"–\").map((time) => {\n const [minutes, seconds] = time.split(\":\").map(Number);\n return minutes * 60 + seconds;\n });\n return { start, end };\n};\n\n/**\n * Map TranscriptCard actorType values to ConversationTurn role values.\n * ConversationTurn uses: \"customer\" | \"agent\" | \"assistant\" | \"system\"\n * TranscriptCard uses: \"customer\" | \"agent\" | \"third_party\" | \"system\"\n */\nconst mapActorTypeToRole = (actorType) => {\n const map = {\n customer: \"customer\",\n agent: \"agent\",\n third_party: \"agent\",\n system: \"system\",\n assistant: \"assistant\",\n };\n return map[actorType] || \"agent\";\n};\n\n/**\n * TranscriptCard Component\n * Displays a complete transcript with multiple turns, audio playback,\n * highlighting, and per-turn play buttons. Composes ConversationTurn\n * for individual turn rendering.\n *\n * @param {Object} props - Component props\n * @param {Array} props.turns - Array of transcript turn objects\n * @param {string} props.turns[].actor - Display name (e.g., \"Agent\", \"Customer\")\n * @param {string} props.turns[].actorType - \"agent\" | \"customer\" | \"third_party\" | \"system\" | \"assistant\"\n * @param {string} [props.turns[].actorColor] - Override rail color for this turn\n * @param {string} props.turns[].text - Transcript text\n * @param {string} [props.turns[].timeRange] - Time range (e.g., \"00:03–00:07\")\n * @param {boolean} [props.turns[].isHighlighted] - Whether this turn is highlighted\n * @param {string} [props.turns[].highlightColor] - Color for the right highlight rail\n * @param {Array} [props.turns[].toolBadges] - Tool badge data [{name, success, pending}]\n * @param {string} [props.audioUrl] - Audio URL for segment playback\n * @param {number} [props.activeTurnIndex=-1] - Index of the externally active turn\n * @param {boolean} [props.autoScrollActiveTurn=false] - Auto-scroll to active turn\n * @param {boolean} [props.isExternalPlaying=false] - External playback state\n * @param {Function} [props.onTurnPlayPause] - External playback handler (turn, index) => void\n */\nexport default function TranscriptCard({\n turns,\n audioUrl,\n activeTurnIndex = -1,\n autoScrollActiveTurn = false,\n isExternalPlaying = false,\n onTurnPlayPause,\n}) {\n const [playingSegment, setPlayingSegment] = useState(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const audioRef = useRef(null);\n const endTimeListenerRef = useRef(null);\n const turnRefs = useRef([]);\n const containerRef = useRef(null);\n const lastAutoScrolledIndexRef = useRef(-1);\n const useExternalPlaybackControl = typeof onTurnPlayPause === \"function\";\n const hasExternalPlaybackState = isExternalPlaying !== undefined;\n\n // Initialize audio element\n useEffect(() => {\n if (audioUrl) {\n audioRef.current = new Audio(audioUrl);\n audioRef.current.preload = \"auto\";\n return () => {\n if (audioRef.current) {\n audioRef.current.pause();\n audioRef.current = null;\n }\n };\n }\n }, [audioUrl]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (audioRef.current) {\n audioRef.current.pause();\n if (endTimeListenerRef.current) {\n audioRef.current.removeEventListener(\"timeupdate\", endTimeListenerRef.current);\n }\n }\n };\n }, []);\n\n // Auto-scroll to active turn\n useEffect(() => {\n if (!autoScrollActiveTurn || activeTurnIndex < 0) return;\n const container = containerRef.current;\n const activeNode = turnRefs.current[activeTurnIndex];\n if (!activeNode || !container) return;\n\n const containerRect = container.getBoundingClientRect();\n const activeRect = activeNode.getBoundingClientRect();\n const topPadding = 12;\n const isFullyVisible =\n activeRect.top >= containerRect.top + topPadding &&\n activeRect.bottom <= containerRect.bottom - 8;\n const didScrollForThisTurn = lastAutoScrolledIndexRef.current === activeTurnIndex;\n\n if (!didScrollForThisTurn || !isFullyVisible) {\n const activeTopInContainer = activeRect.top - containerRect.top + container.scrollTop;\n container.scrollTop = Math.max(activeTopInContainer - topPadding, 0);\n }\n lastAutoScrolledIndexRef.current = activeTurnIndex;\n }, [activeTurnIndex, autoScrollActiveTurn]);\n\n const handlePlayPause = (timeRange, index) => {\n if (useExternalPlaybackControl) {\n onTurnPlayPause(turns[index], index);\n return;\n }\n\n if (playingSegment === index && isPlaying) {\n // Pause current segment\n if (audioRef.current) {\n audioRef.current.pause();\n if (endTimeListenerRef.current) {\n audioRef.current.removeEventListener(\"timeupdate\", endTimeListenerRef.current);\n endTimeListenerRef.current = null;\n }\n }\n setIsPlaying(false);\n } else {\n // Start/switch segment\n const previousSegment = playingSegment;\n setPlayingSegment(index);\n\n if (audioUrl && audioRef.current) {\n const { start, end } = parseTimeRange(timeRange);\n\n if (previousSegment !== null && previousSegment !== index) {\n audioRef.current.pause();\n if (endTimeListenerRef.current) {\n audioRef.current.removeEventListener(\"timeupdate\", endTimeListenerRef.current);\n endTimeListenerRef.current = null;\n }\n }\n\n const playSegment = () => {\n if (!audioRef.current) return;\n audioRef.current.currentTime = start;\n\n if (end) {\n const checkEndTime = () => {\n if (audioRef.current && audioRef.current.currentTime >= end) {\n audioRef.current.pause();\n setIsPlaying(false);\n setPlayingSegment(null);\n if (endTimeListenerRef.current) {\n audioRef.current.removeEventListener(\"timeupdate\", endTimeListenerRef.current);\n endTimeListenerRef.current = null;\n }\n }\n };\n endTimeListenerRef.current = checkEndTime;\n audioRef.current.addEventListener(\"timeupdate\", checkEndTime);\n }\n\n audioRef.current.play()\n .then(() => setIsPlaying(true))\n .catch((err) => {\n // Ignore AbortError which occurs when play() is interrupted by pause()\n if (err && err.name === \"AbortError\") {\n return;\n }\n setIsPlaying(false);\n setPlayingSegment(null);\n });\n };\n\n if (audioRef.current.readyState >= 2) {\n playSegment();\n } else {\n audioRef.current.addEventListener(\"loadedmetadata\", playSegment, { once: true });\n audioRef.current.load();\n }\n } else {\n setIsPlaying(true);\n }\n }\n };\n\n return (\n <div\n ref={containerRef}\n className=\"custom-thin-scrollbar-library\"\n style={{\n maxHeight: 560,\n overflowY: \"auto\",\n scrollBehavior: \"smooth\",\n border: \"1px solid var(--border, rgba(52,58,64,0.12))\",\n // borderRadius: \"var(--radius-lg, 12px)\",\n borderRadius: \"12px 0px 0px 12px\",\n background: \"var(--paper-elevated, rgba(255,255,255,0.82))\",\n padding: 16,\n }}\n >\n {/* Section label */}\n <div\n style={{\n fontSize: \"var(--text-xs-plus, 10.5px)\",\n fontWeight: 650,\n textTransform: \"uppercase\",\n letterSpacing: \"var(--tracking-label, 0.16em)\",\n color: \"var(--text-faint, rgba(30,33,37,0.36))\",\n marginBottom: 12,\n }}\n >\n Transcript\n </div>\n\n {/* Turns */}\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 8 }}>\n {turns.map((turn, index) => {\n const isTurnPlaying = (useExternalPlaybackControl || hasExternalPlaybackState)\n ? Boolean(isExternalPlaying) && activeTurnIndex === index\n : playingSegment === index && isPlaying;\n const isTurnHighlighted = Boolean(turn.isHighlighted) || isTurnPlaying;\n const role = mapActorTypeToRole(turn.actorType);\n\n return (\n <div\n key={index}\n ref={(el) => { turnRefs.current[index] = el; }}\n >\n <ConversationTurn\n role={role}\n text={turn.text}\n actorLabel={turn.actor}\n actorRailColor={turn.actorColor}\n timeRange={turn.timeRange}\n isHighlighted={isTurnHighlighted}\n highlightRailColor={isTurnPlaying \n ? (turn.highlightColor || (role === 'agent' ? 'var(--rail-outcome)' : 'var(--rail-discovery)'))\n : turn.highlightColor}\n toolBadges={turn.toolBadges}\n observations={turn.observations}\n onObservationClick={turn.onObservationClick}\n >\n {/* Play/pause button — rendered inline left of time range */}\n {turn.timeRange ? (\n <button\n onClick={() => handlePlayPause(turn.timeRange, index)}\n style={{\n width: 24,\n height: 24,\n borderRadius: \"50%\",\n border: \"1px solid var(--border, rgba(52,58,64,0.12))\",\n background: isTurnPlaying\n ? \"var(--rail-discovery, #5E88B0)\"\n : \"var(--paper, rgba(255,255,255,0.78))\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"all 0.15s\",\n }}\n aria-label={isTurnPlaying ? `Pause segment ${turn.timeRange}` : `Play segment ${turn.timeRange}`}\n >\n {isTurnPlaying ? (\n <Pause\n style={{ width: 12, height: 12, color: \"var(--paper, #fff)\", fill: \"var(--paper, #fff)\" }}\n strokeWidth={0}\n />\n ) : (\n <Play\n 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 }}\n strokeWidth={0}\n />\n )}\n </button>\n ) : null}\n </ConversationTurn>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"names":["ACTOR_DEFAULTS","KEYFRAMES_ID","ensureKeyframes","style","ConversationTurn","role","text","actorLabel","actorRailColor","actionKicker","toolBadges","streaming","meta","timeRange","isHighlighted","highlightRailColor","observations","onObservationClick","children","useEffect","actor","label","railColor","highlighted","jsx","jsxs","t","i","isPending","isError","dotColor","obs","parseTimeRange","start","end","time","minutes","seconds","mapActorTypeToRole","actorType","TranscriptCard","turns","audioUrl","activeTurnIndex","autoScrollActiveTurn","isExternalPlaying","onTurnPlayPause","playingSegment","setPlayingSegment","useState","isPlaying","setIsPlaying","audioRef","useRef","endTimeListenerRef","turnRefs","containerRef","lastAutoScrolledIndexRef","useExternalPlaybackControl","hasExternalPlaybackState","container","activeNode","containerRect","activeRect","topPadding","isFullyVisible","activeTopInContainer","handlePlayPause","index","previousSegment","playSegment","checkEndTime","err","turn","isTurnPlaying","isTurnHighlighted","el","Pause","Play"],"mappings":"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"}
|