错误处理
SDK 通过四个通道暴露错误。每个通道都有不同的范围和负载——了解在何种场景下使用哪一个,是构建可靠应用的关键。
最后更新于
这有帮助吗?
这有帮助吗?
import { useConvaiClient, DisconnectReasonEnum, getDisconnectReasonMessage } from '@convai/web-sdk';
import { useEffect } from 'react';
function App() {
const client = useConvaiClient({ apiKey: '...', characterId: '...' });
useEffect(() => {
return client.on('disconnect', (reason) => {
console.log(getDisconnectReasonMessage(reason));
switch (reason) {
case DisconnectReasonEnum.CLIENT_INITIATED:
// 用户点击了断开连接——无需处理
break;
case DisconnectReasonEnum.SIGNAL_CLOSE:
case DisconnectReasonEnum.UNKNOWN_REASON:
case DisconnectReasonEnum.STATE_MISMATCH:
// 网络中断——重试
retryWithBackoff(() => client.reconnect());
break;
case DisconnectReasonEnum.SERVER_SHUTDOWN:
setTimeout(() => client.reconnect(), 5000);
break;
case DisconnectReasonEnum.DUPLICATE_IDENTITY:
// 另一个标签页 / 设备接管了——不要自动重试
alert('会话已在另一个窗口中打开。');
break;
case DisconnectReasonEnum.JOIN_FAILURE:
// 配置问题——向用户提示,不要循环重试
alert('加入失败。请检查你的 API key 和角色 ID。');
break;
}
});
}, [client]);
}import { ConvaiClient, DisconnectReasonEnum, getDisconnectReasonMessage } from '@convai/web-sdk/core';
const client = new ConvaiClient({ apiKey: '...', characterId: '...' });
client.on('disconnect', (reason) => {
console.log(getDisconnectReasonMessage(reason));
if (reason !== DisconnectReasonEnum.CLIENT_INITIATED) {
retryWithBackoff(() => client.reconnect());
}
});import type { ServerResponse } from '@convai/web-sdk';
import { useEffect } from 'react';
useEffect(() => {
return client.on('serverResponse', (res: ServerResponse) => {
if (res.status === 'error') {
console.error(`[${res.event_type}] failed: ${res.message}`);
}
// context-update 包含当前令牌预算
if (res.event_type === 'context-update' && res.extras) {
const { token_count, remaining_tokens, max_tokens } = res.extras;
console.log(`上下文:已使用 ${token_count}/${max_tokens} 个 token,剩余 ${remaining_tokens} 个`);
if (remaining_tokens < 5000) {
console.warn('上下文预算不足——请考虑重置临时上下文');
}
}
});
}, [client]);import type { ServerResponse } from '@convai/web-sdk/core';
client.on('serverResponse', (res: ServerResponse) => {
if (res.status === 'error') {
console.error(`[${res.event_type}] failed: ${res.message}`);
}
if (res.event_type === 'context-update' && res.extras) {
const { token_count, remaining_tokens, max_tokens } = res.extras;
console.log(`上下文:已使用 ${token_count}/${max_tokens} 个 token,剩余 ${remaining_tokens} 个`);
}
});
interface ServerResponse {
event_type: string; // 你发送的消息类型
status: 'success' | 'error' | 'processing' | 'pending';
message: string | null; // 出错时非空
extras: ServerResponseExtras | null; // 事件特定数据
}{
token_count: number; // 当前已使用的 token
max_tokens: number; // 预算上限(50 000)
remaining_tokens: number; // 在开始 LRU 裁剪前剩余的 token
}client.on('llmNoResponse', () => {
hideThinkingIndicator();
});client.on('idleWarning', ({ remainingSeconds }) => {
if (remainingSeconds !== null) {
showBanner(`由于不活动,会话将在 ${remainingSeconds} 秒后关闭`);
}
// 在任何用户交互时重置
document.addEventListener('click', () => client.resetIdleTimer(), { once: true });
});async function retryWithBackoff(
fn: () => Promise<void>,
attempts = 3,
delayMs = 500,
): Promise<void> {
for (let i = 1; i <= attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts) throw err;
await new Promise((r) => setTimeout(r, delayMs * 2 ** (i - 1)));
}
}
}
client.on('disconnect', (reason) => {
if (
reason !== DisconnectReasonEnum.CLIENT_INITIATED &&
reason !== DisconnectReasonEnum.DUPLICATE_IDENTITY &&
reason !== DisconnectReasonEnum.JOIN_FAILURE
) {
retryWithBackoff(() => client.reconnect());
}
});function safeSend(client: IConvaiClient, text: string) {
if (!client.state.isConnected || !client.isBotReady) return;
client.sendUserTextMessage(text);
}async function safeToggleMic(client: IConvaiClient) {
try {
await client.audioControls.toggleAudio();
} catch (err) {
console.error('麦克风切换失败:', err);
// 显示权限提示或设备错误消息
}
}// React
useEffect(() => {
const unsubs = [
client.on('error', handleError),
client.on('disconnect', handleDisconnect),
client.on('serverResponse', handleServerResponse),
];
return () => unsubs.forEach((u) => u());
}, [client]);// 原生
const unsubError = client.on('error', handleError);
const unsubDisconnect = client.on('disconnect', handleDisconnect);
// 清理时
unsubError();
unsubDisconnect();