mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-06 16:16:37 +08:00
feat(keys): 按错误码分组展示校验失败的密钥
- **功能增强 (密钥状态页面):** - 在密钥校验结果弹窗中,将校验失败的密钥按照HTTP错误码进行分组展示。 - 每个错误码分组都是可折叠的,并显示该分组下的密钥数量。 - 为每个分组添加了独立的“复制”按钮,方便用户一键复制相同错误类型的所有密钥。 - 优化了错误详情的展示方式,使其在分组内切换,界面更整洁。 - 此项改动极大地改善了当大量密钥校验失败时的用户体验,能够更快速地定位和处理问题。 - **错误修复 (OpenAI服务):** - 修复了 `openai_chat_service.py` 中一个正则表达式的错误。该正则是用于从错误日志中提取HTTP状态码,错误的 `\\d+` 已被修正为 `\d+`,确保能正确捕获状态码。
This commit is contained in:
@@ -511,7 +511,7 @@ class OpenAIChatService:
|
|||||||
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries} with key {current_attempt_key}"
|
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries} with key {current_attempt_key}"
|
||||||
)
|
)
|
||||||
|
|
||||||
match = re.search(r"status code (\\d+)", error_log_msg)
|
match = re.search(r"status code (\d+)", error_log_msg)
|
||||||
if match:
|
if match:
|
||||||
status_code = int(match.group(1))
|
status_code = int(match.group(1))
|
||||||
else:
|
else:
|
||||||
|
|||||||
+128
-51
@@ -502,7 +502,7 @@ function showVerificationResultModal(data) {
|
|||||||
messageElement.appendChild(successDiv);
|
messageElement.appendChild(successDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 失败列表
|
// 失败列表 - 按错误码分组展示
|
||||||
if (Object.keys(failedKeys).length > 0) {
|
if (Object.keys(failedKeys).length > 0) {
|
||||||
const failDiv = document.createElement("div");
|
const failDiv = document.createElement("div");
|
||||||
failDiv.className = "mb-1"; // 减少底部边距
|
failDiv.className = "mb-1"; // 减少底部边距
|
||||||
@@ -531,66 +531,143 @@ function showVerificationResultModal(data) {
|
|||||||
failHeader.appendChild(copyFailBtn);
|
failHeader.appendChild(copyFailBtn);
|
||||||
failDiv.appendChild(failHeader);
|
failDiv.appendChild(failHeader);
|
||||||
|
|
||||||
const failList = document.createElement("ul");
|
// 按错误码分组失败的密钥
|
||||||
failList.className =
|
const errorGroups = {};
|
||||||
"text-sm text-gray-600 max-h-32 overflow-y-auto bg-red-50 p-2 rounded border border-red-200 space-y-1"; // 增加最大高度和间距
|
|
||||||
Object.entries(failedKeys).forEach(([key, error]) => {
|
Object.entries(failedKeys).forEach(([key, error]) => {
|
||||||
const li = document.createElement("li");
|
// 提取错误码或使用完整错误信息作为分组键
|
||||||
// li.className = 'flex justify-between items-center'; // Restore original layout
|
let errorCode = error;
|
||||||
li.className = "flex flex-col items-start"; // Start with vertical layout
|
|
||||||
|
// 尝试提取常见的错误码模式
|
||||||
|
const errorCodePatterns = [
|
||||||
|
/status code (\d+)/,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of errorCodePatterns) {
|
||||||
|
const match = error.match(pattern);
|
||||||
|
if (match) {
|
||||||
|
errorCode = match[1] || match[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有匹配到特定模式,使用500
|
||||||
|
if (errorCode === error) {
|
||||||
|
errorCode = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!errorGroups[errorCode]) {
|
||||||
|
errorGroups[errorCode] = [];
|
||||||
|
}
|
||||||
|
errorGroups[errorCode].push({ key, error });
|
||||||
|
});
|
||||||
|
|
||||||
const keySpanContainer = document.createElement("div");
|
// 创建分组展示容器
|
||||||
keySpanContainer.className = "flex justify-between items-center w-full"; // Ensure key and button are on the same line initially
|
const groupsContainer = document.createElement("div");
|
||||||
|
groupsContainer.className = "space-y-3 max-h-64 overflow-y-auto bg-red-50 p-2 rounded border border-red-200";
|
||||||
|
|
||||||
const keySpan = document.createElement("span");
|
// 按错误码分组展示
|
||||||
keySpan.className = "font-mono";
|
Object.entries(errorGroups).forEach(([errorCode, keyErrorPairs]) => {
|
||||||
// Store full key in dataset, display masked
|
const groupDiv = document.createElement("div");
|
||||||
keySpan.dataset.fullKey = key;
|
groupDiv.className = "border border-red-300 rounded-lg bg-white p-2";
|
||||||
keySpan.textContent =
|
|
||||||
key.substring(0, 4) + "..." + key.substring(key.length - 4);
|
|
||||||
|
|
||||||
const detailsButton = document.createElement("button");
|
// 错误码标题
|
||||||
detailsButton.className =
|
const groupHeader = document.createElement("div");
|
||||||
"ml-2 px-2 py-0.5 bg-red-200 hover:bg-red-300 text-red-700 text-xs rounded transition-colors";
|
groupHeader.className = "flex justify-between items-center mb-2 cursor-pointer";
|
||||||
detailsButton.innerHTML = '<i class="fas fa-info-circle mr-1"></i>详情';
|
groupHeader.innerHTML = `
|
||||||
detailsButton.dataset.error = error; // 将错误信息存储在 data 属性中
|
<div class="flex items-center gap-2">
|
||||||
detailsButton.onclick = (e) => {
|
<i class="fas fa-chevron-down group-toggle-icon text-red-600 transition-transform duration-200"></i>
|
||||||
e.stopPropagation(); // Prevent modal close
|
<h5 class="font-semibold text-red-700 text-sm">错误码: ${errorCode}</h5>
|
||||||
const button = e.currentTarget;
|
<span class="bg-red-100 text-red-600 px-2 py-0.5 rounded-full text-xs font-medium">${keyErrorPairs.length} 个密钥</span>
|
||||||
const listItem = button.closest("li");
|
</div>
|
||||||
const errorMsg = button.dataset.error;
|
<button class="px-2 py-0.5 bg-red-200 hover:bg-red-300 text-red-700 text-xs rounded transition-colors group-copy-btn">
|
||||||
const errorDetailsId = `error-details-${key.replace(
|
<i class="fas fa-copy mr-1"></i>复制组内密钥
|
||||||
/[^a-zA-Z0-9]/g,
|
</button>
|
||||||
""
|
`;
|
||||||
)}`; // Create unique ID
|
|
||||||
let errorDiv = listItem.querySelector(`#${errorDetailsId}`);
|
|
||||||
|
|
||||||
if (errorDiv) {
|
// 复制组内密钥功能
|
||||||
// Collapse: Remove error div and reset li layout
|
const groupCopyBtn = groupHeader.querySelector('.group-copy-btn');
|
||||||
errorDiv.remove();
|
groupCopyBtn.onclick = (e) => {
|
||||||
// listItem.className = 'flex justify-between items-center'; // Restore original layout
|
e.stopPropagation();
|
||||||
listItem.className = "flex flex-col items-start"; // Keep vertical layout
|
const groupKeys = keyErrorPairs.map(pair => pair.key);
|
||||||
button.innerHTML = '<i class="fas fa-info-circle mr-1"></i>详情'; // Restore button text
|
copyToClipboard(groupKeys.join("\n"))
|
||||||
|
.then(() =>
|
||||||
|
showNotification(
|
||||||
|
`已复制 ${groupKeys.length} 个密钥 (错误码: ${errorCode})`,
|
||||||
|
"success"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.catch(() => showNotification("复制失败", "error"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 密钥列表容器
|
||||||
|
const keysList = document.createElement("div");
|
||||||
|
keysList.className = "group-keys-list space-y-1";
|
||||||
|
|
||||||
|
keyErrorPairs.forEach(({ key, error }) => {
|
||||||
|
const keyItem = document.createElement("div");
|
||||||
|
keyItem.className = "flex flex-col items-start bg-gray-50 p-2 rounded border";
|
||||||
|
|
||||||
|
const keySpanContainer = document.createElement("div");
|
||||||
|
keySpanContainer.className = "flex justify-between items-center w-full";
|
||||||
|
|
||||||
|
const keySpan = document.createElement("span");
|
||||||
|
keySpan.className = "font-mono text-sm";
|
||||||
|
keySpan.dataset.fullKey = key;
|
||||||
|
keySpan.textContent = key.substring(0, 4) + "..." + key.substring(key.length - 4);
|
||||||
|
|
||||||
|
const detailsButton = document.createElement("button");
|
||||||
|
detailsButton.className = "ml-2 px-2 py-0.5 bg-red-200 hover:bg-red-300 text-red-700 text-xs rounded transition-colors";
|
||||||
|
detailsButton.innerHTML = '<i class="fas fa-info-circle mr-1"></i>详情';
|
||||||
|
detailsButton.dataset.error = error;
|
||||||
|
detailsButton.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const button = e.currentTarget;
|
||||||
|
const keyItem = button.closest(".bg-gray-50");
|
||||||
|
const errorMsg = button.dataset.error;
|
||||||
|
const errorDetailsId = `error-details-${key.replace(/[^a-zA-Z0-9]/g, "")}`;
|
||||||
|
let errorDiv = keyItem.querySelector(`#${errorDetailsId}`);
|
||||||
|
|
||||||
|
if (errorDiv) {
|
||||||
|
errorDiv.remove();
|
||||||
|
button.innerHTML = '<i class="fas fa-info-circle mr-1"></i>详情';
|
||||||
|
} else {
|
||||||
|
errorDiv = document.createElement("div");
|
||||||
|
errorDiv.id = errorDetailsId;
|
||||||
|
errorDiv.className = "w-full mt-2 text-xs text-red-600 bg-red-50 p-2 rounded border border-red-100 whitespace-pre-wrap break-words";
|
||||||
|
errorDiv.textContent = errorMsg;
|
||||||
|
keyItem.appendChild(errorDiv);
|
||||||
|
button.innerHTML = '<i class="fas fa-chevron-up mr-1"></i>收起';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
keySpanContainer.appendChild(keySpan);
|
||||||
|
keySpanContainer.appendChild(detailsButton);
|
||||||
|
keyItem.appendChild(keySpanContainer);
|
||||||
|
keysList.appendChild(keyItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分组折叠/展开功能
|
||||||
|
groupHeader.onclick = (e) => {
|
||||||
|
if (e.target.closest('.group-copy-btn')) return; // 避免复制按钮触发折叠
|
||||||
|
|
||||||
|
const toggleIcon = groupHeader.querySelector('.group-toggle-icon');
|
||||||
|
const isCollapsed = keysList.style.display === 'none';
|
||||||
|
|
||||||
|
if (isCollapsed) {
|
||||||
|
keysList.style.display = 'block';
|
||||||
|
toggleIcon.style.transform = 'rotate(0deg)';
|
||||||
} else {
|
} else {
|
||||||
// Expand: Create and append error div, change li layout
|
keysList.style.display = 'none';
|
||||||
errorDiv = document.createElement("div");
|
toggleIcon.style.transform = 'rotate(-90deg)';
|
||||||
errorDiv.id = errorDetailsId;
|
|
||||||
errorDiv.className =
|
|
||||||
"w-full mt-1 pl-0 text-xs text-red-600 bg-red-50 p-1 rounded border border-red-100 whitespace-pre-wrap break-words"; // Adjusted padding
|
|
||||||
errorDiv.textContent = errorMsg;
|
|
||||||
listItem.appendChild(errorDiv);
|
|
||||||
listItem.className = "flex flex-col items-start"; // Change layout to vertical
|
|
||||||
button.innerHTML = '<i class="fas fa-chevron-up mr-1"></i>收起'; // Change button text
|
|
||||||
// Move button to be alongside the keySpan for vertical layout (already done)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
keySpanContainer.appendChild(keySpan); // Add keySpan to container
|
groupDiv.appendChild(groupHeader);
|
||||||
keySpanContainer.appendChild(detailsButton); // Add button to container
|
groupDiv.appendChild(keysList);
|
||||||
li.appendChild(keySpanContainer); // Add container to list item
|
groupsContainer.appendChild(groupDiv);
|
||||||
failList.appendChild(li);
|
|
||||||
});
|
});
|
||||||
failDiv.appendChild(failList);
|
|
||||||
|
failDiv.appendChild(groupsContainer);
|
||||||
messageElement.appendChild(failDiv);
|
messageElement.appendChild(failDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user