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.
Files changed (31) 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 +260 -258
  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 +349 -322
  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.es.js +52 -53
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/pages/interactionDetails.cjs.js +2 -2
  21. package/dist/pages/interactionDetails.cjs.js.map +1 -1
  22. package/dist/pages/interactionDetails.es.js +16 -17
  23. package/dist/pages/interactionDetails.es.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/components/UpdatedInteractionDetails/UpdatedInteractionDetails.jsx +35 -1
  26. package/src/components/UpdatedInteractionDetails/UpdatedInteractionRecording.jsx +142 -124
  27. package/src/components/UpdatedInteractionDetails/UpdatedInteractionSignals.jsx +14 -6
  28. package/dist/TranscriptCard.cjs.js +0 -7
  29. package/dist/TranscriptCard.cjs.js.map +0 -1
  30. package/dist/TranscriptCard.es.js +0 -474
  31. 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.3",
4
4
  "description": "Chordia Design System - UI components, tokens, and Tailwind preset",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -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 (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,98 @@ 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 }}>
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
- fontSize: 13, fontWeight: 600,
491
- color: progress > 0 ? 'var(--Green-Primary, #00925F)' : 'var(--Grey-Strong, #2E3236)',
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
- minWidth: 60,
543
+ whiteSpace: 'nowrap',
495
544
  }}>
496
- {fmtTime(displayTime)}
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
- {/* ── Row 3: Speaker timeline bars + playback indicator ── */}
551
- <div style={{ position: 'relative', paddingBottom: 30, cursor: 'pointer' }}>
552
- {['Agent', 'Customer'].map((speaker, rowIdx) => (
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 — Figma: #E3E1D7, stroke w=4 */}
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
- {DEMO_SEGMENTS[speaker].map((seg, i) => (
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: speaker === 'Agent'
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
- {/* ── Playback position: dotted line + time tooltip ── */}
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: 68, /* 60 label + 8 gap */
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
- pointerEvents: 'none',
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
- {/* 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>
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
- <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,
@@ -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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"}