console: play the call recording alongside the transcript

The voice agent uploads a dual-channel WAV of each call to blob storage; until
now the console showed only the transcript's conclusions, not the audio. Add a
player wherever the call is read:

- THE CALL panel on the lead gets an inline "Call recording" player.
- The transcript dialog gets the player above the turns, so a reviewer can
  listen while reading — the words and the audio are the same evidence.

Both read row.call_recording_url (the platform's own field for this, surfaced
on the lead by backend 107) and use preload="none", so a multi-MB WAV is only
fetched when someone actually presses play. The player simply does not render
when a lead has no recording.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-09-09 16:19:26 +05:30
parent bd4ced41ac
commit e0d51159f6
4 changed files with 51 additions and 2 deletions

View File

@ -42,7 +42,7 @@ function turnsFrom(text) {
return out.filter((t) => t.text)
}
export default function CallTranscript({ text, name, onClose }) {
export default function CallTranscript({ text, audio, name, onClose }) {
const ref = useRef(null)
const restore = useRef(null)
const turns = turnsFrom(text)
@ -82,6 +82,16 @@ export default function CallTranscript({ text, name, onClose }) {
</button>
</div>
{audio ? (
<div className="conv__audio">
{/* Listen along with the transcript. preload=none so opening the
dialog does not pull a multi-MB WAV until the user hits play. */}
<audio controls preload="none" src={audio}>
Your browser cannot play this recording.
</audio>
</div>
) : null}
<div className="conv__body">
{turns.length ? turns.map((t, i) => (
<div key={i} className={'bub bub--' + t.side}>

View File

@ -76,3 +76,13 @@
background: var(--zk-tint);
border-radius: var(--r-md);
}
/* The call recording, played from inside the transcript dialog. */
.conv__audio {
padding: 12px 20px 4px;
border-bottom: 1px solid var(--zk-line-soft);
}
.conv__audio audio {
width: 100%;
height: 38px;
}

View File

@ -838,6 +838,17 @@ blocked ? (
Read what was actually said
</button>
) : null}
{title === 'The call' && row.call_recording_url ? (
/* The recording itself, to play alongside the transcript.
preload=none keeps the multi-MB WAV off the wire until
a person actually presses play. */
<div className="glance__rec">
<span className="glance__rec-l">Call recording</span>
<audio controls preload="none" src={row.call_recording_url}>
Your browser cannot play this recording.
</audio>
</div>
) : null}
</section>
))}
</div>
@ -864,7 +875,7 @@ blocked ? (
<AgentCard agentKey={openAgent} onClose={() => setOpenAgent(null)} />
) : null}
{showCall ? (
<CallTranscript text={row.call_transcript} name={row.customer_name} onClose={() => setShowCall(false)} />
<CallTranscript text={row.call_transcript} audio={row.call_recording_url} name={row.customer_name} onClose={() => setShowCall(false)} />
) : null}
{showFile ? (

View File

@ -1727,6 +1727,24 @@
/* An action that belongs to one card, not to the panel. Quiet until hovered:
the notes above it are the answer most of the time, and the transcript is
the follow-up question. */
.glance__rec {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.glance__rec-l {
font-size: var(--fs-3xs);
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--zk-grey);
}
.glance__rec audio {
width: 100%;
height: 36px;
}
.glance__act {
display: inline-flex;
align-items: center;