引入與使用說明
概述
本文檔提供 AICC Agent JS SDK 的實際使用指南,包括加載 SDK、初始化、完整的座席應用示例,以及常見場景的代碼實現。
SDK 加載
方式 1:通過 CDN 加載
html
<script src="https://your-server-ip/static/uni4cc.js"></script>方式 2:通過 NPM 安裝
bash
npm install @aicc/uni4cc-sdkjavascript
import { init as initSdk } from '@aicc/uni4cc-sdk';
const sdk = initSdk({
server: 'wss://your-server-ip:8443',
// ... 其他配置
});方式 3:使用完整的 HTML 模板
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>AICC 座席工作台</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<!-- UI 元件 -->
</div>
<script src="https://your-server-ip/static/uni4cc.js"></script>
<script src="app.js"></script>
</body>
</html>初始化示例
基本初始化
javascript
// 初始化 SDK
const sdk = uni4cc.init({
// 必填參數
server: 'wss://your-server-ip:8443',
agentId: 'agent_001',
agentName: '王小明',
agentTeam: '業務部',
// Janus Gateway 配置
janus: {
server: 'wss://your-server-ip:8443/janus'
},
// 可選參數
debug: true, // 是否啟用調試模式
logLevel: 'info', // 日誌級別
heartbeatInterval: 30000, // 心跳間隔 (ms)
reconnectMaxAttempts: 10, // 最大重連次數
reconnectDelay: 1000 // 重連延遲 (ms)
});
console.log('SDK 已初始化');完整座席應用示例
HTML 結構
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>AICC 座席工作台</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
background: #007bff;
color: white;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
.status-panel {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
margin-bottom: 20px;
}
.status-item {
background: #f0f0f0;
padding: 15px;
border-radius: 5px;
}
.call-panel {
background: #fff;
border: 1px solid #ddd;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
.call-controls {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
button {
padding: 10px;
border: none;
border-radius: 5px;
background: #007bff;
color: white;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #0056b3;
}
button.danger {
background: #dc3545;
}
button.success {
background: #28a745;
}
button.warning {
background: #ffc107;
color: black;
}
input, select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.call-info {
background: #e7f3ff;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
display: none;
}
.call-info.show {
display: block;
}
.alert {
padding: 15px;
margin: 10px 0;
border-radius: 5px;
display: none;
}
.alert.show {
display: block;
}
.alert-info {
background: #cfe2ff;
border: 1px solid #b6d4fe;
color: #084298;
}
.alert-error {
background: #f8d7da;
border: 1px solid #f5c6cb;
color: #842029;
}
.transcript {
background: #fff;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
max-height: 300px;
overflow-y: auto;
margin-top: 20px;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>AICC 座席工作台</h1>
<p>座席: <span id="agentName">未登錄</span> | 狀態: <span id="agentStatus">OFFLINE</span></p>
</div>
<!-- 登錄面板 -->
<div id="loginPanel" class="call-panel">
<h3>座席登錄</h3>
<div>
<label>密碼:</label>
<input type="password" id="passwordInput" placeholder="輸入密碼">
</div>
<div>
<label>登錄類型:</label>
<select id="loginType">
<option value="1">座席</option>
<option value="2">監督者</option>
<option value="3">管理員</option>
</select>
</div>
<div>
<label>工作類型:</label>
<select id="workType">
<option value="1">入站</option>
<option value="2">出站</option>
<option value="3">混合</option>
</select>
</div>
<button onclick="doLogin()">登錄</button>
</div>
<!-- 狀態面板 -->
<div id="statusPanel" class="status-panel" style="display:none;">
<div class="status-item">
<strong>座席狀態</strong>
<p id="statusDisplay">-</p>
</div>
<div class="status-item">
<strong>通話狀態</strong>
<p id="callStatus">無通話</p>
</div>
<div class="status-item">
<strong>通話時長</strong>
<p id="callDuration">00:00</p>
</div>
</div>
<!-- 警告信息 -->
<div id="alertBox" class="alert alert-info"></div>
<!-- 通話控制面板 -->
<div id="callPanel" class="call-panel" style="display:none;">
<h3>通話控制</h3>
<!-- 就緒/非就緒 -->
<div style="margin-bottom: 20px;">
<h4>座席狀態</h4>
<button class="success" onclick="doSetReady()">設置就緒</button>
<button class="warning" onclick="doSetNotReady()">設置非就緒</button>
<button class="danger" onclick="doLogout()">登出</button>
</div>
<!-- 撥號控制 -->
<div style="margin-bottom: 20px;">
<h4>撥號</h4>
<div>
<input type="text" id="dialNumber" placeholder="輸入電話號碼">
<button onclick="doMakeCall()">撥號</button>
</div>
</div>
<!-- 通話控制 -->
<div style="margin-bottom: 20px;">
<h4>通話控制</h4>
<div class="call-controls">
<button onclick="doAnswerCall()">接聽</button>
<button onclick="doMuteCall()">靜音</button>
<button onclick="doUnmuteCall()">取消靜音</button>
<button onclick="doHoldCall()">保持</button>
<button onclick="doResumeCall()">恢復</button>
<button onclick="doTransferCall()">轉接</button>
<button onclick="doConsultCall()">諮詢</button>
<button onclick="doHangupCall()">掛斷</button>
</div>
</div>
<!-- 通話信息 -->
<div id="callInfo" class="call-info">
<p><strong>來電:</strong> <span id="incomingFrom">-</span></p>
<p><strong>顯示名稱:</strong> <span id="callerName">-</span></p>
</div>
</div>
<!-- 語音識別轉錄 -->
<div id="transcriptPanel" style="display:none;">
<h3>通話記錄</h3>
<div id="transcript" class="transcript"></div>
</div>
</div>
<script src="https://your-server-ip/static/uni4cc.js"></script>
<script src="app.js"></script>
</body>
</html>JavaScript 實現 (app.js)
javascript
let sdk;
let currentCallId = null;
let transcriptBuffer = [];
// 初始化 SDK
function initSDK() {
sdk = uni4cc.init({
server: 'wss://your-server-ip:8443',
agentId: 'agent_001',
agentName: '王小明',
agentTeam: '業務部',
janus: {
server: 'wss://your-server-ip:8443/janus'
},
debug: true,
logLevel: 'info'
});
// 監聽所有事件
setupEventListeners();
console.log('SDK 已初始化');
}
// 設置事件監聽
function setupEventListeners() {
// 座席事件
sdk.on('loginSuccess', onLoginSuccess);
sdk.on('loginFailed', onLoginFailed);
sdk.on('logoutSuccess', onLogoutSuccess);
sdk.on('statusChanged', onStatusChanged);
// 通話事件
sdk.on('incomingCall', onIncomingCall);
sdk.on('callStateChanged', onCallStateChanged);
sdk.on('callEnded', onCallEnded);
sdk.on('muteStateChanged', onMuteStateChanged);
sdk.on('holdStateChanged', onHoldStateChanged);
// 連接事件
sdk.on('connectionLost', onConnectionLost);
sdk.on('connectionRestored', onConnectionRestored);
// 語音識別
sdk.on('speechRecognized', onSpeechRecognized);
// 錯誤
sdk.on('error', onError);
}
// 登錄
async function doLogin() {
const password = document.getElementById('passwordInput').value;
const loginType = parseInt(document.getElementById('loginType').value);
const workType = parseInt(document.getElementById('workType').value);
if (!password) {
showAlert('請輸入密碼', 'error');
return;
}
try {
await sdk.login(password, loginType, workType);
showAlert('登錄成功', 'info');
} catch (error) {
showAlert('登錄失敗: ' + error.message, 'error');
}
}
// 登錄成功
function onLoginSuccess(event) {
console.log('登錄成功:', event.agentName);
document.getElementById('agentName').textContent = event.agentName;
document.getElementById('loginPanel').style.display = 'none';
document.getElementById('statusPanel').style.display = 'grid';
document.getElementById('callPanel').style.display = 'block';
document.getElementById('transcriptPanel').style.display = 'block';
updateTranscript('座席已登錄');
}
// 登錄失敗
function onLoginFailed(event) {
showAlert('登錄失敗: ' + event.message, 'error');
}
// 登出
async function doLogout() {
try {
await sdk.logout();
document.getElementById('loginPanel').style.display = 'block';
document.getElementById('statusPanel').style.display = 'none';
document.getElementById('callPanel').style.display = 'none';
document.getElementById('transcriptPanel').style.display = 'none';
showAlert('已登出', 'info');
} catch (error) {
showAlert('登出失敗: ' + error.message, 'error');
}
}
// 設置就緒
async function doSetReady() {
try {
await sdk.setReady();
showAlert('已設置為就緒', 'info');
} catch (error) {
showAlert('設置失敗: ' + error.message, 'error');
}
}
// 設置非就緒
async function doSetNotReady() {
const reason = prompt('請輸入非就緒原因 (留空為無):', '');
try {
await sdk.setNotReady(reason || undefined);
showAlert('已設置為非就緒', 'info');
} catch (error) {
showAlert('設置失敗: ' + error.message, 'error');
}
}
// 撥號
async function doMakeCall() {
const to = document.getElementById('dialNumber').value;
if (!to) {
showAlert('請輸入電話號碼', 'error');
return;
}
try {
await sdk.makeCall({ to });
showAlert('撥號中...', 'info');
} catch (error) {
showAlert('撥號失敗: ' + error.message, 'error');
}
}
// 接聽
async function doAnswerCall() {
try {
await sdk.answerCall();
showAlert('已接聽', 'info');
} catch (error) {
showAlert('接聽失敗: ' + error.message, 'error');
}
}
// 靜音
async function doMuteCall() {
try {
await sdk.muteCall();
showAlert('已靜音', 'info');
} catch (error) {
showAlert('靜音失敗: ' + error.message, 'error');
}
}
// 取消靜音
async function doUnmuteCall() {
try {
await sdk.unmuteCall();
showAlert('已取消靜音', 'info');
} catch (error) {
showAlert('取消靜音失敗: ' + error.message, 'error');
}
}
// 保持
async function doHoldCall() {
try {
await sdk.holdCall();
showAlert('通話已保持', 'info');
} catch (error) {
showAlert('保持失敗: ' + error.message, 'error');
}
}
// 恢復
async function doResumeCall() {
try {
await sdk.resumeCall();
showAlert('通話已恢復', 'info');
} catch (error) {
showAlert('恢復失敗: ' + error.message, 'error');
}
}
// 轉接
async function doTransferCall() {
const to = prompt('請輸入轉接目標 (座席 ID 或分機):', '');
if (!to) return;
try {
await sdk.transferCall({ to });
showAlert('已發起轉接', 'info');
} catch (error) {
showAlert('轉接失敗: ' + error.message, 'error');
}
}
// 諮詢
async function doConsultCall() {
const to = prompt('請輸入諮詢對象 (座席 ID 或分機):', '');
if (!to) return;
try {
await sdk.consultCall({ to });
showAlert('已發起諮詢', 'info');
} catch (error) {
showAlert('諮詢失敗: ' + error.message, 'error');
}
}
// 掛斷
async function doHangupCall() {
try {
const result = await sdk.hangupCall();
showAlert('通話已掛斷,時長: ' + result.duration + '秒', 'info');
currentCallId = null;
} catch (error) {
showAlert('掛斷失敗: ' + error.message, 'error');
}
}
// 事件處理
function onStatusChanged(event) {
const statusText = {
'OFFLINE': '未登錄',
'LOGGEDIN': '已登錄',
'READY': '就緒中',
'NOT_READY': '非就緒',
'TALKING': '通話中',
'HOLDING': '保持中'
};
document.getElementById('agentStatus').textContent =
statusText[event.status] || event.status;
document.getElementById('statusDisplay').textContent =
statusText[event.status] || event.status;
updateTranscript('狀態變更: ' + event.status);
}
function onIncomingCall(event) {
currentCallId = event.callId;
document.getElementById('incomingFrom').textContent = event.from;
document.getElementById('callerName').textContent = event.displayName || '-';
document.getElementById('callInfo').classList.add('show');
updateTranscript('來電: ' + event.displayName + ' (' + event.from + ')');
playAlert();
}
function onCallStateChanged(event) {
currentCallId = event.callId;
const stateText = {
'INIT': '初始',
'DIALING': '撥號中',
'CONNECTING': '連接中',
'CONNECTED': '已連接',
'HOLDING': '保持中',
'TRANSFER': '轉接中',
'CONSULT': '諮詢中'
};
document.getElementById('callStatus').textContent =
stateText[event.state] || event.state;
updateTranscript('通話狀態: ' + event.state);
}
function onCallEnded(event) {
document.getElementById('callInfo').classList.remove('show');
document.getElementById('callStatus').textContent = '無通話';
document.getElementById('callDuration').textContent = '00:00';
updateTranscript(
'通話結束,時長: ' + event.duration + '秒,掛機原因: ' + event.hangupReason
);
currentCallId = null;
}
function onMuteStateChanged(event) {
const status = event.muted ? '已靜音' : '已取消靜音';
updateTranscript(status);
}
function onHoldStateChanged(event) {
const status = event.onHold ? '通話已保持' : '通話已恢復';
updateTranscript(status);
}
function onConnectionLost(event) {
showAlert('連接已丟失,正在重新連接...', 'error');
}
function onConnectionRestored(event) {
showAlert('連接已恢復', 'info');
}
function onSpeechRecognized(event) {
const speaker = event.speaker === 'agent' ? '座席' : '客戶';
updateTranscript(speaker + ': ' + event.text);
}
function onError(event) {
showAlert('錯誤: ' + event.message, 'error');
console.error('SDK Error:', event);
}
function onLogoutSuccess(event) {
showAlert('已登出', 'info');
}
// 輔助函數
function showAlert(message, type) {
const alertBox = document.getElementById('alertBox');
alertBox.textContent = message;
alertBox.className = 'alert show alert-' + type;
setTimeout(() => {
alertBox.classList.remove('show');
}, 5000);
}
function updateTranscript(message) {
const transcript = document.getElementById('transcript');
const timestamp = new Date().toLocaleTimeString();
const entry = timestamp + ' - ' + message;
transcriptBuffer.push(entry);
transcript.innerHTML = transcriptBuffer.join('<br>');
transcript.scrollTop = transcript.scrollHeight;
}
function playAlert() {
// 播放提示音
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = 800;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.01,
audioContext.currentTime + 0.5
);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.5);
}
// 定期更新通話時長
setInterval(() => {
if (currentCallId) {
const callInfo = sdk.getCallInfo(currentCallId);
if (callInfo && callInfo.state === 'CONNECTED') {
const minutes = Math.floor(callInfo.duration / 60);
const seconds = callInfo.duration % 60;
document.getElementById('callDuration').textContent =
String(minutes).padStart(2, '0') + ':' +
String(seconds).padStart(2, '0');
}
}
}, 1000);
// 頁面加載時初始化
window.addEventListener('load', initSDK);
// 頁面卸載前登出
window.addEventListener('beforeunload', async () => {
if (sdk) {
await sdk.logout();
}
});使用最佳實踐
1. 錯誤處理
javascript
async function safeCall(fn, ...args) {
try {
return await fn(...args);
} catch (error) {
console.error('操作失敗:', error);
showAlert('操作失敗: ' + error.message, 'error');
}
}
// 使用
await safeCall(sdk.makeCall, { to: '0912345678' });2. 狀態檢查
javascript
function canAnswer() {
const status = sdk.getStatus();
return status && status.status === 'READY';
}
async function doAnswerCall() {
if (!canAnswer()) {
showAlert('座席未就緒,無法接聽', 'error');
return;
}
await sdk.answerCall();
}3. 資源清理
javascript
window.addEventListener('beforeunload', async () => {
// 登出
try {
await sdk.logout();
} catch (error) {
console.error('登出失敗:', error);
}
// 取消所有事件監聽
sdk.off('incomingCall');
sdk.off('statusChanged');
// ... 其他事件
});4. 日誌和監控
javascript
function setupMonitoring() {
sdk.on('connectionLost', (event) => {
console.warn('[Monitor] 連接丟失', event);
reportMetric('connection_lost', 1);
});
sdk.on('callEnded', (event) => {
reportMetric('call_ended', 1);
reportMetric('call_duration', event.duration);
});
}
function reportMetric(name, value) {
// 發送到分析系統
fetch('/api/metrics', {
method: 'POST',
body: JSON.stringify({ name, value, timestamp: Date.now() })
});
}注意事項
- 安全性:不要在代碼中硬編碼密碼
- 性能:避免在回調函數中進行耗時操作
- 記憶體洩漏:記得取消事件監聽和清理資源
- 瀏覽器相容性:測試目標瀏覽器的支持情況
- 網絡穩定性:實現自動重連和斷線重連邏輯