mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
feat: call control, recording, CDR, missed calls, live call assist
- Call Control API (CONFERENCE/HOLD/MUTE/KICK_CALL) - Recording pause/unpause - Fetch CDR Detailed (call history with recordings) - Abandon Calls (missed calls from Ozonetel) - Call Assist WebSocket gateway (Deepgram STT + OpenAI suggestions) - Call Assist service (lead context loading) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -193,6 +193,165 @@ export class OzonetelAgentService {
|
||||
}
|
||||
}
|
||||
|
||||
async callControl(params: {
|
||||
action: 'CONFERENCE' | 'HOLD' | 'UNHOLD' | 'MUTE' | 'UNMUTE' | 'KICK_CALL';
|
||||
ucid: string;
|
||||
conferenceNumber?: string;
|
||||
}): Promise<{ status: string; message: string; ucid?: string }> {
|
||||
const url = `https://${this.apiDomain}/ca_apis/CallControl_V4`;
|
||||
const did = process.env.OZONETEL_DID ?? '918041763265';
|
||||
const agentPhoneName = process.env.OZONETEL_SIP_ID ?? '523590';
|
||||
|
||||
this.logger.log(`Call control: action=${params.action} ucid=${params.ucid} conference=${params.conferenceNumber ?? 'none'}`);
|
||||
|
||||
try {
|
||||
const token = await this.getToken();
|
||||
const body: Record<string, string> = {
|
||||
userName: this.accountId,
|
||||
action: params.action,
|
||||
ucid: params.ucid,
|
||||
did,
|
||||
agentPhoneName,
|
||||
};
|
||||
if (params.conferenceNumber) {
|
||||
body.conferenceNumber = params.conferenceNumber;
|
||||
}
|
||||
|
||||
const response = await axios.post(url, body, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(`Call control response: ${JSON.stringify(response.data)}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
|
||||
this.logger.error(`Call control failed: ${error.message} ${responseData}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async pauseRecording(params: {
|
||||
ucid: string;
|
||||
action: 'pause' | 'unPause';
|
||||
}): Promise<{ status: string; message: string }> {
|
||||
const url = `https://${this.apiDomain}/CAServices/Call/Record.php`;
|
||||
|
||||
this.logger.log(`Recording ${params.action}: ucid=${params.ucid}`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
userName: this.accountId,
|
||||
apiKey: this.apiKey,
|
||||
action: params.action,
|
||||
ucid: params.ucid,
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(`Recording control response: ${JSON.stringify(response.data)}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
|
||||
this.logger.error(`Recording control failed: ${error.message} ${responseData}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getAbandonCalls(params?: {
|
||||
fromTime?: string;
|
||||
toTime?: string;
|
||||
campaignName?: string;
|
||||
}): Promise<Array<{
|
||||
monitorUCID: string;
|
||||
type: string;
|
||||
status: string;
|
||||
campaign: string;
|
||||
callerID: string;
|
||||
did: string;
|
||||
agentID: string;
|
||||
agent: string;
|
||||
hangupBy: string;
|
||||
callTime: string;
|
||||
}>> {
|
||||
const url = `https://${this.apiDomain}/ca_apis/abandonCalls`;
|
||||
|
||||
this.logger.log('Fetching abandon calls');
|
||||
|
||||
try {
|
||||
const token = await this.getToken();
|
||||
const body: Record<string, string> = { userName: this.accountId };
|
||||
if (params?.fromTime) body.fromTime = params.fromTime;
|
||||
if (params?.toTime) body.toTime = params.toTime;
|
||||
if (params?.campaignName) body.campaignName = params.campaignName;
|
||||
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
this.logger.log(`Abandon calls response: ${JSON.stringify(data).substring(0, 200)}`);
|
||||
if (data.status === 'success' && Array.isArray(data.message)) {
|
||||
return data.message;
|
||||
}
|
||||
return [];
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Abandon calls failed: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async fetchCDR(params: {
|
||||
date: string; // YYYY-MM-DD
|
||||
campaignName?: string;
|
||||
status?: string;
|
||||
callType?: string;
|
||||
}): Promise<Array<Record<string, any>>> {
|
||||
const url = `https://${this.apiDomain}/ca_reports/fetchCDRDetails`;
|
||||
|
||||
this.logger.log(`Fetch CDR: date=${params.date}`);
|
||||
|
||||
try {
|
||||
const token = await this.getToken();
|
||||
const body: Record<string, string> = {
|
||||
userName: this.accountId,
|
||||
fromDate: `${params.date} 00:00:00`,
|
||||
toDate: `${params.date} 23:59:59`,
|
||||
};
|
||||
if (params.campaignName) body.campaignName = params.campaignName;
|
||||
if (params.status) body.status = params.status;
|
||||
if (params.callType) body.callType = params.callType;
|
||||
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
if (data.status === 'success' && Array.isArray(data.details)) {
|
||||
return data.details;
|
||||
}
|
||||
return [];
|
||||
} catch (error: any) {
|
||||
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
|
||||
this.logger.error(`Fetch CDR failed: ${error.message} ${responseData}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async logoutAgent(params: {
|
||||
agentId: string;
|
||||
password: string;
|
||||
|
||||
Reference in New Issue
Block a user