-
Notifications
You must be signed in to change notification settings - Fork 380
fix(ivr-transcript): implement-ivr-transcript #4448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from 2 commits
c1427f8
95e1613
2b10c3b
47052ac
91dfca1
89275f8
0798925
9f2645e
b0b6bfc
d8e8fb1
8fe1800
5055706
c4f84ad
005cc39
8b5dd9e
97b8057
d2167aa
7acc899
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -79,6 +79,16 @@ const agentLoginInputError = document.getElementById('agent-login-input-error'); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const applyupdateAgentProfileBtn = document.querySelector('#applyupdateAgentProfile'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const autoWrapupTimerElm = document.getElementById('autoWrapupTimer'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const timerValueElm = autoWrapupTimerElm.querySelector('.timer-value'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const fetchIvrTranscriptBtn = document.querySelector('#fetch-ivr-transcript'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ivrTimeoutInput = document.querySelector('#timeout-mins'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ivrStatusElm = document.querySelector('#ivr-transcript-status'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ivrResultsContentElm = document.querySelector('#ivr-conversation-list'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('IVR Transcript UI elements initialized:', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fetchButton: !!fetchIvrTranscriptBtn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
timeoutInput: !!ivrTimeoutInput, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
statusElement: !!ivrStatusElm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
resultsElement: !!ivrResultsContentElm | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
deregisterBtn.style.backgroundColor = 'red'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Store and Grab `access-token` from sessionStorage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -584,6 +594,142 @@ function refreshUIPostConsult() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hideEndConsultButton(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Function to update IVR transcript button state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function updateIvrTranscriptButtonState() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('Updating IVR transcript button state...'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!currentTask) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('IVR Button State: No active task found'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fetchIvrTranscriptBtn.disabled = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ivrStatusElm.textContent = 'No active task'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('IVR current task ', currentTask); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log(`IVR Button State: Task status: ${currentTask.data.interaction.state}, Media channel: ${currentTask.data.mediaType}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (currentTask.data.interaction.state === 'connected' && currentTask.data.mediaType === 'telephony') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('IVR Button State: Enabling button - task accepted and telephony channel'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fetchIvrTranscriptBtn.disabled = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ivrStatusElm.textContent = 'Ready to fetch IVR transcript'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if (currentTask.data.interaction.state !== 'connected') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('IVR Button State: Disabling button - task not accepted'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fetchIvrTranscriptBtn.disabled = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ivrStatusElm.textContent = 'Task must be accepted first'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if (currentTask.data.mediaType !== 'telephony') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('IVR Button State: Disabling button - not telephony channel'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fetchIvrTranscriptBtn.disabled = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ivrStatusElm.textContent = 'IVR transcript only available for telephony'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: using currentTask.data.mediaType (should be currentTask.data.interaction.mediaType) Task data stores media type under data.interaction.mediaType throughout the sample. Using data.mediaType will mis-detect channel and disable the button incorrectly. Apply this diff: - console.log('IVR current task ', currentTask);
- console.log(`IVR Button State: Task status: ${currentTask.data.interaction.state}, Media channel: ${currentTask.data.mediaType}`);
+ console.log('IVR current task ', currentTask);
+ console.log(`IVR Button State: Task status: ${currentTask.data.interaction.state}, Media channel: ${currentTask.data.interaction.mediaType}`);
- if (currentTask.data.interaction.state === 'connected' && currentTask.data.mediaType === 'telephony') {
+ if (currentTask.data.interaction.state === 'connected' && currentTask.data.interaction.mediaType === 'telephony') {
console.log('IVR Button State: Enabling button - task accepted and telephony channel');
fetchIvrTranscriptBtn.disabled = false;
ivrStatusElm.textContent = 'Ready to fetch IVR transcript';
} else if (currentTask.data.interaction.state !== 'connected') {
console.log('IVR Button State: Disabling button - task not accepted');
fetchIvrTranscriptBtn.disabled = true;
ivrStatusElm.textContent = 'Task must be accepted first';
- } else if (currentTask.data.mediaType !== 'telephony') {
+ } else if (currentTask.data.interaction.mediaType !== 'telephony') {
console.log('IVR Button State: Disabling button - not telephony channel');
fetchIvrTranscriptBtn.disabled = true;
ivrStatusElm.textContent = 'IVR transcript only available for telephony';
} 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Function to fetch IVR transcript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async function fetchIvrTranscript() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('=== Starting IVR Transcript Fetch ==='); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Check if task is accepted and media type is telephony | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!currentTask) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.error('IVR Fetch: No active task found'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ivrStatusElm.textContent = 'No active task found'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log(`IVR Fetch: Current task status: ${currentTask.data.interaction.state}, media channel: ${currentTask.data.mediaType}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (currentTask.data.interaction.state !== 'connected') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.warn('IVR Fetch: Task is not accepted'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ivrStatusElm.textContent = 'Task must be accepted to fetch IVR transcript'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (currentTask.data.mediaType !== 'telephony') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.warn('IVR Fetch: Media channel is not telephony'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ivrStatusElm.textContent = 'IVR transcript is only available for telephony tasks'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
// Function to fetch IVR transcript | |
async function fetchIvrTranscript() { | |
console.log('=== Starting IVR Transcript Fetch ==='); | |
// Check if task is accepted and media type is telephony | |
if (!currentTask) { | |
console.error('IVR Fetch: No active task found'); | |
ivrStatusElm.textContent = 'No active task found'; | |
return; | |
} | |
console.log(`IVR Fetch: Current task status: ${currentTask.data.interaction.state}, media channel: ${currentTask.data.mediaType}`); | |
if (currentTask.data.interaction.state !== 'connected') { | |
console.warn('IVR Fetch: Task is not accepted'); | |
ivrStatusElm.textContent = 'Task must be accepted to fetch IVR transcript'; | |
return; | |
} | |
if (currentTask.data.mediaType !== 'telephony') { | |
console.warn('IVR Fetch: Media channel is not telephony'); | |
ivrStatusElm.textContent = 'IVR transcript is only available for telephony tasks'; | |
return; | |
// Function to fetch IVR transcript | |
async function fetchIvrTranscript() { | |
console.log('=== Starting IVR Transcript Fetch ==='); | |
// Check if task is accepted and media type is telephony | |
if (!currentTask) { | |
console.error('IVR Fetch: No active task found'); | |
ivrStatusElm.textContent = 'No active task found'; | |
return; | |
} | |
console.log(`IVR Fetch: Current task status: ${currentTask.data.interaction.state}, media channel: ${currentTask.data.interaction.mediaType}`); | |
if (currentTask.data.interaction.state !== 'connected') { | |
console.warn('IVR Fetch: Task is not accepted'); | |
ivrStatusElm.textContent = 'Task must be accepted to fetch IVR transcript'; | |
return; | |
} | |
if (currentTask.data.interaction.mediaType !== 'telephony') { | |
console.warn('IVR Fetch: Media channel is not telephony'); | |
ivrStatusElm.textContent = 'IVR transcript is only available for telephony tasks'; | |
return; | |
} | |
// …rest of function… | |
} |
🤖 Prompt for AI Agents
In docs/samples/contact-center/app.js around lines 625 to 647, the precondition
checks for media type are incorrectly reading currentTask.data.mediaType instead
of the mediaType nested under interaction; update the code to reference
currentTask.data.interaction.mediaType everywhere in this function (including
the console.log and the if-check that validates 'telephony') so the
media-channel checks use interaction.mediaType consistently and
ivrStatus/messages remain accurate.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XSS risk: unescaped user text inserted via innerHTML
Transcript fields (e.g., customer query) originate from user speech-to-text and must be treated as untrusted. Direct innerHTML interpolation is vulnerable to XSS. Escape values or assign via textContent/DOM nodes.
Apply minimal escaping in this block:
- <p><strong>Call ID:</strong> ${conversation.callId || 'N/A'}</p>
- <p><strong>Duration:</strong> ${conversation.duration || 'N/A'}</p>
- <p><strong>Start Time:</strong> ${conversation.startTime || 'N/A'}</p>
- <p><strong>End Time:</strong> ${conversation.endTime || 'N/A'}</p>
+ <p><strong>Call ID:</strong> ${escapeHtml(conversation.callId || 'N/A')}</p>
+ <p><strong>Duration:</strong> ${escapeHtml(conversation.duration || 'N/A')}</p>
+ <p><strong>Start Time:</strong> ${escapeHtml(conversation.startTime || 'N/A')}</p>
+ <p><strong>End Time:</strong> ${escapeHtml(conversation.endTime || 'N/A')}</p>
@@
- ${conversation.segments.map(segment => `
+ ${conversation.segments.map(segment => `
<li style="margin-bottom: 5px;">
- <strong>${segment.speaker || 'Unknown'}:</strong> ${segment.text || 'N/A'}
- <br><small>Duration: ${segment.duration || 'N/A'}, Offset: ${segment.offset || 'N/A'}</small>
+ <strong>${escapeHtml(segment.speaker || 'Unknown')}:</strong> ${escapeHtml(segment.text || 'N/A')}
+ <br><small>Duration: ${escapeHtml(segment.duration || 'N/A')}, Offset: ${escapeHtml(segment.offset || 'N/A')}</small>
</li>
`).join('')}
Place this helper near the top of the file (or before usage):
// Escapes HTML special characters to prevent XSS in sample UI
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Note: The service returns IvrConversations as an array of turns ({customer?}/{bot?}). Consider rendering those turns directly in the UI instead of assuming conversation.segments/callId/duration, which may be undefined.
🤖 Prompt for AI Agents
In docs/samples/contact-center/app.js around lines 676-705, the code injects
transcript data directly into innerHTML leading to XSS risk; add an escapeHtml
helper near the top of the file (before usage) that replaces &, <, >, ", ' with
their HTML entities, then update this block to avoid raw innerHTML interpolation
by either building DOM nodes and setting textContent for all user-derived fields
(callId, duration, startTime, endTime, segment.speaker, segment.text, etc.) or,
if continuing to build a string, pass every user-derived value through
escapeHtml before inserting it; also handle missing fields safely and prefer
rendering turns (customer/bot) if available instead of assuming
segments/callId/duration exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check all the console logs and keep only relevant ones
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed irrelevant logs and kept only the necessary ones.