修复token余额不足时仍能继续使用功能的问题

根因:后端扣费用的是 fallback_amount=200(最低消费),用户剩14个token
时 14<200 导致扣费失败,但原有逻辑只在 remaining<=0 时才终止会话和
通知前端,remaining=14>0 时扣费失败被静默忽略。

修复:
- 后端 voice_chat_router.py:扣费失败时(charge 返回 success=false)
  始终终止事件循环并发送 error 事件,不再只判断 remaining<=0
- 后端 chat_router.py:文字聊天扣费失败时也发送 error 事件通知前端
- 前端 chatStore.js:token_update 消息带 warning 字段时(扣费失败),
  标记 _realtimeTokenExhausted=true,断开 WebSocket,阻止继续使用

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
wsb1224 2026-06-07 14:05:21 +08:00
parent ab26e64af6
commit f8e70e3fd5
3 changed files with 15 additions and 9 deletions

View File

@ -274,11 +274,11 @@ async def websocket_chat_endpoint(websocket: WebSocket, user_id: str):
)
# 余额耗尽时通知前端
remaining = charge_result.get("available_tokens", 0)
if remaining <= 0:
await websocket.send_text(json.dumps({
# 扣费失败时也通知前端
await websocket.send_text(json.dumps({
"type": "error",
"error": "insufficient_tokens",
"message": "Token不足,请充值",
"message": "Token不足(剩余" + str(remaining) + ",请充值",
"available_tokens": remaining,
"timestamp": datetime.now().isoformat()
}))

View File

@ -1264,8 +1264,8 @@ async def receive_events_loop(
}))
except Exception:
pass
# 余额耗尽时终止事件循环,防止火山引擎继续消费
if remaining <= 0:
# 扣费失败时终止事件循环(余额不够最低消费也要停)
if True:
logger.warning(
f"[VOICE_TOKEN_EXHAUSTED] user_id={user_id} mode={charge_mode} "
f"remaining={remaining}, breaking event loop and closing"

View File

@ -1013,6 +1013,7 @@ export const useChatStore = defineStore('chat', () => {
case 'token_update':
{
const remaining = data.remaining_tokens ?? data.data?.remaining_tokens ?? null
const warning = data.warning || data.data?.warning
if (remaining !== null) {
console.log('[ChatStore] Token update:', remaining, 'remaining')
// 余额耗尽时标记并断开
@ -1024,16 +1025,21 @@ export const useChatStore = defineStore('chat', () => {
resetAudioState()
break
}
// 扣费失败(余额不够最低消费)也标记耗尽
if (warning) {
console.warn('[ChatStore] Token charge failed:', warning, 'remaining:', remaining)
_realtimeTokenExhausted = true
ElMessage.error(`Token不足剩余${remaining}),实时通话已结束`)
disconnectRealtimeWebSocket()
resetAudioState()
break
}
// 充值后恢复:余额恢复时重置耗尽标记和 AI 处理状态
_realtimeTokenExhausted = false
if (isAiProcessing.value) {
isAiProcessing.value = false
}
}
const warning = data.warning || data.data?.warning
if (warning) {
console.warn('[ChatStore] Token warning:', warning)
}
}
break
case 'error':