mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: call recording analysis with Deepgram diarization + AI insights
- Deepgram pre-recorded API: transcription with diarization, sentiment, topics, summary - OpenAI structured insights: call outcome, patient satisfaction, coaching notes, action items, compliance flags - Slideout panel UI with audio player, speaker-labeled transcript, sentiment badge - AI pill button in recordings table between Caller and Type columns - Redis caching (7-day TTL) to avoid re-analyzing the same recording Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { faMagnifyingGlass, faPlay, faPause } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { faMagnifyingGlass, faPlay, faPause, faSparkles } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faIcon } from '@/lib/icon-wrapper';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Input } from '@/components/base/input/input';
|
||||
import { Table } from '@/components/application/table/table';
|
||||
import { TopBar } from '@/components/layout/top-bar';
|
||||
import { PhoneActionCell } from '@/components/call-desk/phone-action-cell';
|
||||
import { RecordingAnalysisSlideout } from '@/components/call-desk/recording-analysis';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import { formatPhone, formatDateOnly } from '@/lib/format';
|
||||
|
||||
@@ -63,6 +64,7 @@ export const CallRecordingsPage = () => {
|
||||
const [calls, setCalls] = useState<RecordingRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState('');
|
||||
const [slideoutCallId, setSlideoutCallId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
apiClient.graphql<{ calls: { edges: Array<{ node: RecordingRecord }> } }>(QUERY, undefined, { silent: true })
|
||||
@@ -110,6 +112,7 @@ export const CallRecordingsPage = () => {
|
||||
<Table.Header>
|
||||
<Table.Head label="Agent" isRowHeader />
|
||||
<Table.Head label="Caller" />
|
||||
<Table.Head label="AI" className="w-14" />
|
||||
<Table.Head label="Type" className="w-20" />
|
||||
<Table.Head label="Date" className="w-28" />
|
||||
<Table.Head label="Duration" className="w-20" />
|
||||
@@ -132,6 +135,21 @@ export const CallRecordingsPage = () => {
|
||||
<PhoneActionCell phoneNumber={phone} displayNumber={formatPhone({ number: phone, callingCode: '+91' })} />
|
||||
) : <span className="text-xs text-quaternary">—</span>}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
setSlideoutCallId(call.id);
|
||||
}}
|
||||
className="inline-flex items-center gap-1 rounded-full bg-brand-primary px-2.5 py-1 text-xs font-semibold text-brand-secondary hover:bg-brand-secondary hover:text-white cursor-pointer transition duration-100 ease-linear"
|
||||
title="AI Analysis"
|
||||
>
|
||||
<FontAwesomeIcon icon={faSparkles} className="size-3" />
|
||||
AI
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Badge size="sm" color={dirColor} type="pill-color">{dirLabel}</Badge>
|
||||
</Table.Cell>
|
||||
@@ -159,7 +177,28 @@ export const CallRecordingsPage = () => {
|
||||
</Table.Body>
|
||||
</Table>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* Analysis slideout */}
|
||||
{(() => {
|
||||
const call = slideoutCallId ? filtered.find(c => c.id === slideoutCallId) : null;
|
||||
if (!call?.recording?.primaryLinkUrl) return null;
|
||||
return (
|
||||
<RecordingAnalysisSlideout
|
||||
isOpen={true}
|
||||
onOpenChange={(open) => { if (!open) setSlideoutCallId(null); }}
|
||||
recordingUrl={call.recording.primaryLinkUrl}
|
||||
callId={call.id}
|
||||
agentName={call.agentName}
|
||||
callerNumber={call.callerNumber?.primaryPhoneNumber ?? null}
|
||||
direction={call.direction}
|
||||
startedAt={call.startedAt}
|
||||
durationSec={call.durationSec}
|
||||
disposition={call.disposition}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user