mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 08:59:16 +00:00
feat: 改进管理界面弹窗体验和滚动条美化
- 修复API Key创建/编辑弹窗和账户信息修改弹窗在低高度屏幕上被遮挡的问题 - 为所有弹窗添加自适应高度支持,最大高度限制为90vh - 美化Claude账户弹窗的滚动条样式,使用紫蓝渐变色与主题保持一致 - 添加响应式适配,移动设备上弹窗高度调整为85vh - 优化滚动条交互体验,支持悬停和激活状态 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,35 @@ class ClaudeRelayService {
|
||||
clientResponse.removeListener('close', handleClientDisconnect);
|
||||
}
|
||||
|
||||
// 检查响应是否为限流错误
|
||||
if (response.statusCode !== 200 && response.statusCode !== 201) {
|
||||
let isRateLimited = false;
|
||||
try {
|
||||
const responseBody = typeof response.body === 'string' ? JSON.parse(response.body) : response.body;
|
||||
if (responseBody && responseBody.error && responseBody.error.message &&
|
||||
responseBody.error.message.toLowerCase().includes('exceed your account\'s rate limit')) {
|
||||
isRateLimited = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果解析失败,检查原始字符串
|
||||
if (response.body && response.body.toLowerCase().includes('exceed your account\'s rate limit')) {
|
||||
isRateLimited = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRateLimited) {
|
||||
logger.warn(`🚫 Rate limit detected for account ${accountId}, status: ${response.statusCode}`);
|
||||
// 标记账号为限流状态并删除粘性会话映射
|
||||
await claudeAccountService.markAccountRateLimited(accountId, sessionHash);
|
||||
}
|
||||
} else if (response.statusCode === 200 || response.statusCode === 201) {
|
||||
// 如果请求成功,检查并移除限流状态
|
||||
const isRateLimited = await claudeAccountService.isAccountRateLimited(accountId);
|
||||
if (isRateLimited) {
|
||||
await claudeAccountService.removeAccountRateLimit(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录成功的API调用
|
||||
const inputTokens = requestBody.messages ?
|
||||
requestBody.messages.reduce((sum, msg) => sum + (msg.content?.length || 0), 0) / 4 : 0; // 粗略估算
|
||||
@@ -408,7 +437,7 @@ class ClaudeRelayService {
|
||||
const proxyAgent = await this._getProxyAgent(accountId);
|
||||
|
||||
// 发送流式请求并捕获usage数据
|
||||
return await this._makeClaudeStreamRequestWithUsageCapture(processedBody, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback);
|
||||
return await this._makeClaudeStreamRequestWithUsageCapture(processedBody, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback, accountId, sessionHash);
|
||||
} catch (error) {
|
||||
logger.error('❌ Claude stream relay with usage capture failed:', error);
|
||||
throw error;
|
||||
@@ -416,7 +445,7 @@ class ClaudeRelayService {
|
||||
}
|
||||
|
||||
// 🌊 发送流式请求到Claude API(带usage数据捕获)
|
||||
async _makeClaudeStreamRequestWithUsageCapture(body, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback) {
|
||||
async _makeClaudeStreamRequestWithUsageCapture(body, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback, accountId, sessionHash) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(this.claudeApiUrl);
|
||||
|
||||
@@ -457,6 +486,7 @@ class ClaudeRelayService {
|
||||
let buffer = '';
|
||||
let finalUsageReported = false; // 防止重复统计的标志
|
||||
let collectedUsageData = {}; // 收集来自不同事件的usage数据
|
||||
let rateLimitDetected = false; // 限流检测标志
|
||||
|
||||
// 监听数据块,解析SSE并寻找usage信息
|
||||
res.on('data', (chunk) => {
|
||||
@@ -517,6 +547,13 @@ class ClaudeRelayService {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有限流错误
|
||||
if (data.type === 'error' && data.error && data.error.message &&
|
||||
data.error.message.toLowerCase().includes('exceed your account\'s rate limit')) {
|
||||
rateLimitDetected = true;
|
||||
logger.warn(`🚫 Rate limit detected in stream for account ${accountId}`);
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
// 忽略JSON解析错误,继续处理
|
||||
logger.debug('🔍 SSE line not JSON or no usage data:', line.slice(0, 100));
|
||||
@@ -525,7 +562,7 @@ class ClaudeRelayService {
|
||||
}
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
res.on('end', async () => {
|
||||
// 处理缓冲区中剩余的数据
|
||||
if (buffer.trim()) {
|
||||
responseStream.write(buffer);
|
||||
@@ -537,6 +574,18 @@ class ClaudeRelayService {
|
||||
logger.warn('⚠️ Stream completed but no usage data was captured! This indicates a problem with SSE parsing or Claude API response format.');
|
||||
}
|
||||
|
||||
// 处理限流状态
|
||||
if (rateLimitDetected || res.statusCode === 429) {
|
||||
// 标记账号为限流状态并删除粘性会话映射
|
||||
await claudeAccountService.markAccountRateLimited(accountId, sessionHash);
|
||||
} else if (res.statusCode === 200) {
|
||||
// 如果请求成功,检查并移除限流状态
|
||||
const isRateLimited = await claudeAccountService.isAccountRateLimited(accountId);
|
||||
if (isRateLimited) {
|
||||
await claudeAccountService.removeAccountRateLimit(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug('🌊 Claude stream response with usage capture completed');
|
||||
resolve();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user