@coze/uniapp-api
Version:
Official Coze UniApp SDK for seamless AI integration into your applications | 扣子官方 UniApp SDK,助您轻松集成 AI 能力到应用中
229 lines (228 loc) • 10 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WsChatClient = exports.WsChatEventNames = void 0;
const api_1 = require("@coze/api");
const pcm_recorder_1 = require("../pcm-recorder");
const event_names_1 = require("./event-names");
Object.defineProperty(exports, "WsChatEventNames", { enumerable: true, get: function () { return event_names_1.WsChatEventNames; } });
const base_1 = __importDefault(require("./base"));
/**
* WebSocket Chat Client for WeChat Mini Program
* Implements real-time chat functionality with audio support
*/
class WsChatClient extends base_1.default {
/**
* Create a new WsChatClient instance
* @param {any} config - Configuration options
*/
constructor(config) {
var _a;
super(config);
Object.defineProperty(this, "recorder", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "isMuted", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "turnDetection", {
enumerable: true,
configurable: true,
writable: true,
value: 'server_vad'
});
this.recorder = new pcm_recorder_1.PcmRecorder({
sampleRate: 16000,
debug: true,
});
this.isMuted = (_a = config.audioMutedDefault) !== null && _a !== void 0 ? _a : false;
}
/**
* Start recording audio and sending audio data to server
* @private
*/
startRecord() {
if (this.recorder.getStatus() === pcm_recorder_1.RecordingStatus.RECORDING) {
console.warn('Recorder is already recording');
return;
}
// 如果是客户端判停,需要先取消当前的播放
if (this.turnDetection === 'client_interrupt') {
this.interrupt();
}
// 1. Start recorder
this.recorder.start();
// Initialize audio playback
// await this.wavStreamPlayer.add16BitPCM(new ArrayBuffer(0), this.trackId);
// 2. Register audio data callback
this.recorder.record({
pcmAudioCallback: data => {
var _a;
const { raw } = data;
// Convert ArrayBuffer to base64 string for UniApp
const base64String = this.arrayBufferToBase64(raw);
// Send audio to WebSocket
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send({
id: Date.now().toString(),
event_type: api_1.WebsocketsEventType.INPUT_AUDIO_BUFFER_APPEND,
data: {
delta: base64String,
},
});
},
});
}
/**
* Stop recording audio
*/
stopRecord() {
var _a;
if (this.recorder.getStatus() !== pcm_recorder_1.RecordingStatus.RECORDING) {
console.warn('Recorder is not recording');
return;
}
this.recorder.destroy();
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send({
id: Date.now().toString(),
event_type: api_1.WebsocketsEventType.INPUT_AUDIO_BUFFER_COMPLETE,
});
}
/**
* Convert ArrayBuffer to Base64 string for WeChat Mini Program
* @param {ArrayBuffer} buffer - The buffer to convert
* @returns {string} - Base64 string
*/
arrayBufferToBase64(buffer) {
// WeChat Mini Program provides a utility function for this conversion
return uni.arrayBufferToBase64(buffer);
}
/**
* Connect to the chat server and start recording if not muted
* @param {Object} options - Connection options
* @param {ChatUpdateEvent} [options.chatUpdate] - Initial chat update event
* @returns {Promise<void>} - Promise that resolves when connected
*/
connect() {
return __awaiter(this, arguments, void 0, function* ({ chatUpdate, } = {}) {
var _a, _b, _c, _d, _e, _f, _g;
const ws = yield this.init();
this.ws = ws;
const sampleRate = this.recorder.getSampleRate();
console.log('sampleRate', sampleRate);
const event = {
id: (chatUpdate === null || chatUpdate === void 0 ? void 0 : chatUpdate.id) || Date.now().toString(),
event_type: api_1.WebsocketsEventType.CHAT_UPDATE,
data: Object.assign({ input_audio: {
format: 'pcm',
codec: 'pcm',
sample_rate: sampleRate,
}, output_audio: {
codec: 'pcm',
pcm_config: {
sample_rate: 24000,
},
voice_id: this.config.voiceId || undefined,
}, turn_detection: {
type: 'server_vad',
}, need_play_prologue: true }, chatUpdate === null || chatUpdate === void 0 ? void 0 : chatUpdate.data),
};
this.ws.send(event);
// 设置音频播放器的默认格式和采样率
this.wavStreamPlayer.setDefaultFormat(((_b = (_a = event.data) === null || _a === void 0 ? void 0 : _a.output_audio) === null || _b === void 0 ? void 0 : _b.codec) || 'g711a');
this.wavStreamPlayer.setSampleRate(((_e = (_d = (_c = event.data) === null || _c === void 0 ? void 0 : _c.output_audio) === null || _d === void 0 ? void 0 : _d.pcm_config) === null || _e === void 0 ? void 0 : _e.sample_rate) || 8000);
// 判停模式,server_vad(服务端判停) 或 client_vad(客户端判停,需自行调用 startRecord/stopRecord)
this.turnDetection = ((_g = (_f = event.data) === null || _f === void 0 ? void 0 : _f.turn_detection) === null || _g === void 0 ? void 0 : _g.type) || 'server_vad';
console.debug('chat.update:', event);
// Start recording if not muted && not client_vad
if (!this.isMuted && this.turnDetection !== 'client_interrupt') {
yield this.startRecord();
}
this.emit(event_names_1.WsChatEventNames.CONNECTED, event);
});
}
/**
* Disconnect from the chat server and clean up resources
* @returns {Promise<void>} - Promise that resolves when disconnected
*/
disconnect() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send({
id: Date.now().toString(),
event_type: api_1.WebsocketsEventType.CONVERSATION_CHAT_CANCEL,
});
this.recorder.destroy();
this.emit(event_names_1.WsChatEventNames.DISCONNECTED, undefined);
yield new Promise(resolve => setTimeout(resolve, 500));
this.listeners.clear();
this.closeWs();
});
}
/**
* Enable or disable audio input
* @param {boolean} enable - Whether to enable audio
* @returns {Promise<void>} - Promise that resolves when the operation completes
*/
setAudioEnable(enable) {
return __awaiter(this, void 0, void 0, function* () {
if (this.turnDetection === 'client_interrupt') {
throw new Error('Client VAD mode does not support setAudioEnable');
}
const status = this.recorder.getStatus();
if (enable) {
if (status === pcm_recorder_1.RecordingStatus.IDLE) {
yield this.startRecord();
this.isMuted = false;
this.emit(event_names_1.WsChatEventNames.AUDIO_UNMUTED, undefined);
}
else if (status === pcm_recorder_1.RecordingStatus.PAUSED) {
yield this.recorder.resume();
this.isMuted = false;
this.emit(event_names_1.WsChatEventNames.AUDIO_UNMUTED, undefined);
}
else {
this.warn('Recorder is already active with status', status);
}
}
else {
if (status === pcm_recorder_1.RecordingStatus.RECORDING) {
yield this.recorder.pause();
this.isMuted = true;
this.emit(event_names_1.WsChatEventNames.AUDIO_MUTED, undefined);
}
else {
this.warn('Recorder is not recording with status', status);
}
}
});
}
/**
* Interrupt the current conversation
*/
interrupt() {
var _a;
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send({
id: Date.now().toString(),
event_type: api_1.WebsocketsEventType.CONVERSATION_CHAT_CANCEL,
});
this.emit(event_names_1.WsChatEventNames.INTERRUPTED, undefined);
}
}
exports.WsChatClient = WsChatClient;