diff --git a/relay/channel/openai/relay_responses.go b/relay/channel/openai/relay_responses.go
index 88fb88083f50..e188889e43a2 100644
--- a/relay/channel/openai/relay_responses.go
+++ b/relay/channel/openai/relay_responses.go
@@ -46,9 +46,17 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
usage.PromptTokensDetails.CachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens
}
}
+ if info == nil || info.ResponsesUsageInfo == nil || info.ResponsesUsageInfo.BuiltInTools == nil {
+ return &usage, nil
+ }
// 解析 Tools 用量
for _, tool := range responsesResponse.Tools {
- info.ResponsesUsageInfo.BuiltInTools[common.Interface2String(tool["type"])].CallCount++
+ buildToolinfo, ok := info.ResponsesUsageInfo.BuiltInTools[common.Interface2String(tool["type"])]
+ if !ok || buildToolinfo == nil {
+ logger.LogError(c, fmt.Sprintf("BuiltInTools not found for tool type: %v", tool["type"]))
+ continue
+ }
+ buildToolinfo.CallCount++
}
return &usage, nil
}
@@ -72,7 +80,7 @@ func OaiResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
sendResponsesStreamData(c, streamResponse, data)
switch streamResponse.Type {
case "response.completed":
- if streamResponse.Response.Usage != nil {
+ if streamResponse.Response != nil && streamResponse.Response.Usage != nil {
if streamResponse.Response.Usage.InputTokens != 0 {
usage.PromptTokens = streamResponse.Response.Usage.InputTokens
}
diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx
index 7a86fa1141a8..4a66f69a4913 100644
--- a/web/src/components/table/channels/modals/EditChannelModal.jsx
+++ b/web/src/components/table/channels/modals/EditChannelModal.jsx
@@ -892,6 +892,56 @@ const EditChannelModal = (props) => {
}
};
+ // 密钥去重函数
+ const deduplicateKeys = () => {
+ const currentKey = formApiRef.current?.getValue('key') || inputs.key || '';
+
+ if (!currentKey.trim()) {
+ showInfo(t('请先输入密钥'));
+ return;
+ }
+
+ // 按行分割密钥
+ const keyLines = currentKey.split('\n');
+ const beforeCount = keyLines.length;
+
+ // 使用哈希表去重,保持原有顺序
+ const keySet = new Set();
+ const deduplicatedKeys = [];
+
+ keyLines.forEach((line) => {
+ const trimmedLine = line.trim();
+ if (trimmedLine && !keySet.has(trimmedLine)) {
+ keySet.add(trimmedLine);
+ deduplicatedKeys.push(trimmedLine);
+ }
+ });
+
+ const afterCount = deduplicatedKeys.length;
+ const deduplicatedKeyText = deduplicatedKeys.join('\n');
+
+ // 更新表单和状态
+ if (formApiRef.current) {
+ formApiRef.current.setValue('key', deduplicatedKeyText);
+ }
+ handleInputChange('key', deduplicatedKeyText);
+
+ // 显示去重结果
+ const message = t(
+ '去重完成:去重前 {{before}} 个密钥,去重后 {{after}} 个密钥',
+ {
+ before: beforeCount,
+ after: afterCount,
+ },
+ );
+
+ if (beforeCount === afterCount) {
+ showInfo(t('未发现重复密钥'));
+ } else {
+ showSuccess(message);
+ }
+ };
+
const addCustomModels = () => {
if (customModel.trim() === '') return;
const modelArray = customModel.split(',').map((model) => model.trim());
@@ -987,24 +1037,42 @@ const EditChannelModal = (props) => {
)}
{batch && (
-