Skip to content

引入與使用說明

概述

本文檔提供 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-sdk
javascript
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() })
  });
}

注意事項

  1. 安全性:不要在代碼中硬編碼密碼
  2. 性能:避免在回調函數中進行耗時操作
  3. 記憶體洩漏:記得取消事件監聽和清理資源
  4. 瀏覽器相容性:測試目標瀏覽器的支持情況
  5. 網絡穩定性:實現自動重連和斷線重連邏輯

承暉資訊資源中心