133 lines
4.9 KiB
JavaScript
133 lines
4.9 KiB
JavaScript
const { Signer } = require('@volcengine/openapi');
|
||
const fetch = require('node-fetch');
|
||
const { AccessToken, privileges } = require('../lib/token');
|
||
|
||
class VolcengineService {
|
||
constructor() {
|
||
this.baseUrl = 'https://rtc.volcengineapi.com';
|
||
this.service = 'rtc';
|
||
this.region = 'cn-north-1';
|
||
this.version = '2024-12-01';
|
||
}
|
||
|
||
async startVoiceChat(config) {
|
||
console.log('[Volcengine] Starting voice chat (S2S端到端 + LLM混合, API v2024-12-01)');
|
||
console.log('[Volcengine] RoomId:', config.RoomId);
|
||
// ProviderParams 可能是 JSON 字符串或对象
|
||
let pp = config.Config.S2SConfig?.ProviderParams;
|
||
if (typeof pp === 'string') {
|
||
try { pp = JSON.parse(pp); } catch (e) { pp = {}; }
|
||
}
|
||
console.log('[Volcengine] S2S AppId:', pp?.app?.appid);
|
||
console.log('[Volcengine] S2S model:', pp?.dialog?.extra?.model);
|
||
console.log('[Volcengine] S2S speaker:', pp?.tts?.speaker);
|
||
console.log('[Volcengine] ProviderParams type:', typeof config.Config.S2SConfig?.ProviderParams);
|
||
console.log('[Volcengine] LLM EndPointId:', config.Config.LLMConfig?.EndPointId);
|
||
console.log('[Volcengine] Tools:', config.Config.LLMConfig?.Tools?.length || 0);
|
||
console.log('[Volcengine] Full request body:', JSON.stringify(config, null, 2));
|
||
const result = await this._callOpenAPI('StartVoiceChat', config);
|
||
console.log('[Volcengine] StartVoiceChat response:', JSON.stringify(result, null, 2));
|
||
return result;
|
||
}
|
||
|
||
async updateVoiceChat(params) {
|
||
console.log('[Volcengine] Updating voice chat (v2024-12-01)');
|
||
console.log('[Volcengine] UpdateVoiceChat params:', JSON.stringify(params, null, 2));
|
||
const result = await this._callOpenAPI('UpdateVoiceChat', params);
|
||
console.log('[Volcengine] UpdateVoiceChat response:', JSON.stringify(result, null, 2));
|
||
return result;
|
||
}
|
||
|
||
async stopVoiceChat(params) {
|
||
console.log('[Volcengine] Stopping voice chat, RoomId:', params.RoomId);
|
||
return this._callOpenAPI('StopVoiceChat', params);
|
||
}
|
||
|
||
/**
|
||
* 生成 RTC 入房 Token
|
||
* 使用官方 AccessToken 库:https://github.com/volcengine/rtc-aigc-demo/blob/main/Server/token.js
|
||
*/
|
||
generateRTCToken(roomId, userId) {
|
||
const appId = process.env.VOLC_RTC_APP_ID;
|
||
const appKey = process.env.VOLC_RTC_APP_KEY;
|
||
|
||
if (!appId || !appKey || appKey === 'your_rtc_app_key') {
|
||
console.warn('[Volcengine] RTC AppKey not configured, returning placeholder token');
|
||
return `placeholder_token_${roomId}_${userId}_${Date.now()}`;
|
||
}
|
||
|
||
const token = new AccessToken(appId, appKey, roomId, userId);
|
||
const expireTime = Math.floor(Date.now() / 1000) + 24 * 3600; // 24 小时有效
|
||
token.expireTime(expireTime);
|
||
token.addPrivilege(privileges.PrivPublishStream, 0);
|
||
token.addPrivilege(privileges.PrivSubscribeStream, 0);
|
||
|
||
const serialized = token.serialize();
|
||
console.log(`[Volcengine] RTC Token generated for room=${roomId}, user=${userId}`);
|
||
return serialized;
|
||
}
|
||
|
||
async _callOpenAPI(action, body, versionOverride) {
|
||
const ak = process.env.VOLC_ACCESS_KEY_ID;
|
||
const sk = process.env.VOLC_SECRET_ACCESS_KEY;
|
||
const version = versionOverride || this.version;
|
||
|
||
if (!ak || !sk || ak === 'your_access_key_id') {
|
||
console.warn(`[Volcengine] Credentials not configured, returning mock response for ${action}`);
|
||
return this._mockResponse(action, body);
|
||
}
|
||
|
||
// 与官方 rtc-aigc-demo 完全一致的签名方式
|
||
const openApiRequestData = {
|
||
region: this.region,
|
||
method: 'POST',
|
||
params: {
|
||
Action: action,
|
||
Version: version,
|
||
},
|
||
headers: {
|
||
Host: 'rtc.volcengineapi.com',
|
||
'Content-type': 'application/json',
|
||
},
|
||
body,
|
||
};
|
||
|
||
const signer = new Signer(openApiRequestData, this.service);
|
||
signer.addAuthorization({ accessKeyId: ak, secretKey: sk });
|
||
|
||
const url = `${this.baseUrl}?Action=${action}&Version=${version}`;
|
||
console.log(`[Volcengine] ${action} calling:`, url);
|
||
|
||
try {
|
||
const response = await fetch(url, {
|
||
method: 'POST',
|
||
headers: openApiRequestData.headers,
|
||
body: JSON.stringify(body),
|
||
});
|
||
const data = await response.json();
|
||
|
||
if (data?.ResponseMetadata?.Error) {
|
||
const err = data.ResponseMetadata.Error;
|
||
throw new Error(`${action} failed: ${err.Code} - ${err.Message}`);
|
||
}
|
||
return data;
|
||
} catch (error) {
|
||
console.error(`[Volcengine] ${action} error:`, error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Mock 响应(开发阶段凭证未配置时使用)
|
||
*/
|
||
_mockResponse(action, params) {
|
||
console.log(`[Volcengine][MOCK] ${action} called with:`, JSON.stringify(params, null, 2).substring(0, 500));
|
||
return {
|
||
ResponseMetadata: { RequestId: `mock-${Date.now()}`, Action: action },
|
||
Result: { Message: 'Mock response - credentials not configured' },
|
||
};
|
||
}
|
||
}
|
||
|
||
module.exports = new VolcengineService();
|