fix: 品牌保护+知识库全量覆盖 - 6层防御解决传销问题 + 30+产品关键词补全

This commit is contained in:
User
2026-03-17 11:00:09 +08:00
parent f97dd7e3d5
commit 0560db1048
46 changed files with 1948 additions and 120 deletions

View File

@@ -18,6 +18,7 @@ class NativeVoiceService {
onError: null,
onAssistantPending: null,
onDiagnostic: null,
onIdleTimeout: null,
};
}
@@ -76,7 +77,7 @@ class NativeVoiceService {
}
}
async connect({ sessionId, userId, botName, systemRole, speakingStyle, modelVersion, speaker }) {
async connect({ sessionId, userId, botName, systemRole, speakingStyle, modelVersion, speaker, greetingText }) {
await this.disconnect();
const wsUrl = this.resolveWebSocketUrl(sessionId, userId);
this.emitConnectionState('connecting');
@@ -86,6 +87,22 @@ class NativeVoiceService {
}
this.playbackTime = this.playbackContext.currentTime;
// 并行: 同时预获取麦克风和建立WS连接节省500ms+
const micPromise = navigator.mediaDevices.getUserMedia({
audio: {
channelCount: 1,
noiseSuppression: true,
echoCancellation: true,
autoGainControl: true,
},
video: false,
}).catch((err) => {
console.warn('[NativeVoice] Pre-fetch getUserMedia failed:', err.message);
return null;
});
const CONNECTION_TIMEOUT_MS = 12000;
await new Promise((resolve, reject) => {
this.readyResolver = resolve;
this.readyRejector = reject;
@@ -93,6 +110,18 @@ class NativeVoiceService {
ws.binaryType = 'arraybuffer';
this.ws = ws;
// 超时兜底:避免无限等待
const timeoutId = setTimeout(() => {
if (this.readyResolver) {
console.warn(`[NativeVoice] Connection timeout (${CONNECTION_TIMEOUT_MS}ms), forcing ready`);
this.readyResolver();
this.readyResolver = null;
this.readyRejector = null;
}
}, CONNECTION_TIMEOUT_MS);
const clearTimeoutOnSettle = () => clearTimeout(timeoutId);
ws.onopen = () => {
this.emitConnectionState('connected');
ws.send(JSON.stringify({
@@ -104,10 +133,12 @@ class NativeVoiceService {
speakingStyle,
modelVersion,
speaker,
greetingText,
}));
};
ws.onerror = () => {
clearTimeoutOnSettle();
const error = new Error('WebSocket connection failed');
this.callbacks.onError?.(error);
this.readyRejector?.(error);
@@ -117,6 +148,7 @@ class NativeVoiceService {
};
ws.onclose = () => {
clearTimeoutOnSettle();
this.emitConnectionState('disconnected');
if (this.readyRejector) {
this.readyRejector(new Error('WebSocket closed before ready'));
@@ -127,14 +159,20 @@ class NativeVoiceService {
ws.onmessage = (event) => {
if (typeof event.data === 'string') {
this.handleJsonMessage(event.data);
const peek = event.data;
if (peek.includes('"ready"')) {
clearTimeoutOnSettle();
}
this.handleJsonMessage(peek);
return;
}
this.handleAudioMessage(event.data);
};
});
await this.startCapture();
// 使用预获取的mediaStream已并行获取避免重复申请
const preFetchedStream = await micPromise;
await this.startCapture(preFetchedStream);
}
handleJsonMessage(raw) {
@@ -164,6 +202,14 @@ class NativeVoiceService {
this.callbacks.onAssistantPending?.(!!msg.active);
return;
}
if (msg.type === 'idle_timeout') {
this.callbacks.onIdleTimeout?.(msg.timeout || 300000);
return;
}
if (msg.type === 'upstream_closed') {
this.callbacks.onError?.(new Error('语音服务已断开,请重新开始通话'));
return;
}
if (msg.type === 'error') {
this.callbacks.onError?.(new Error(msg.error || 'native voice error'));
return;
@@ -206,8 +252,8 @@ class NativeVoiceService {
this.emitDiagnostic('audio_chunk', { samples: pcm16.length, duration: audioBuffer.duration });
}
async startCapture() {
this.mediaStream = await navigator.mediaDevices.getUserMedia({
async startCapture(preFetchedStream) {
this.mediaStream = preFetchedStream || await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: 1,
noiseSuppression: true,
@@ -274,6 +320,13 @@ class NativeVoiceService {
});
}
requestGreetingReplay() {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'replay_greeting' }));
this.emitDiagnostic('replay_greeting', { sent: true });
}
}
async disconnect() {
if (this.captureProcessor) {
this.captureProcessor.disconnect();