diff --git a/relay/channel/gemini/dto.go b/relay/channel/gemini/dto.go index 6403ffbc..fa9108df 100644 --- a/relay/channel/gemini/dto.go +++ b/relay/channel/gemini/dto.go @@ -1,5 +1,7 @@ package gemini +import "encoding/json" + type GeminiChatRequest struct { Contents []GeminiChatContent `json:"contents"` SafetySettings []GeminiChatSafetySettings `json:"safetySettings,omitempty"` @@ -22,6 +24,30 @@ type GeminiInlineData struct { Data string `json:"data"` } +// UnmarshalJSON custom unmarshaler for GeminiInlineData to support snake_case and camelCase for MimeType +func (g *GeminiInlineData) UnmarshalJSON(data []byte) error { + type Alias GeminiInlineData // Use type alias to avoid recursion + var aux struct { + Alias + MimeTypeSnake string `json:"mime_type"` + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + *g = GeminiInlineData(aux.Alias) // Copy other fields if any in future + + // Prioritize snake_case if present + if aux.MimeTypeSnake != "" { + g.MimeType = aux.MimeTypeSnake + } else if aux.MimeType != "" { // Fallback to camelCase from Alias + g.MimeType = aux.MimeType + } + // g.Data would be populated by aux.Alias.Data + return nil +} + type FunctionCall struct { FunctionName string `json:"name"` Arguments any `json:"args"` @@ -58,6 +84,33 @@ type GeminiPart struct { CodeExecutionResult *GeminiPartCodeExecutionResult `json:"codeExecutionResult,omitempty"` } +// UnmarshalJSON custom unmarshaler for GeminiPart to support snake_case and camelCase for InlineData +func (p *GeminiPart) UnmarshalJSON(data []byte) error { + // Alias to avoid recursion during unmarshalling + type Alias GeminiPart + var aux struct { + Alias + InlineDataSnake *GeminiInlineData `json:"inline_data,omitempty"` // snake_case variant + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Assign fields from alias + *p = GeminiPart(aux.Alias) + + // Prioritize snake_case for InlineData if present + if aux.InlineDataSnake != nil { + p.InlineData = aux.InlineDataSnake + } else if aux.InlineData != nil { // Fallback to camelCase from Alias + p.InlineData = aux.InlineData + } + // Other fields like Text, FunctionCall etc. are already populated via aux.Alias + + return nil +} + type GeminiChatContent struct { Role string `json:"role,omitempty"` Parts []GeminiPart `json:"parts"` diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index 45c41e60..e2288faf 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -205,7 +205,22 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon } else if val, exists := tool_call_ids[message.ToolCallId]; exists { name = val } - contentMap := common.StrToMap(message.StringContent()) + var contentMap map[string]interface{} + contentStr := message.StringContent() + + // 1. 尝试解析为 JSON 对象 + if err := json.Unmarshal([]byte(contentStr), &contentMap); err != nil { + // 2. 如果失败,尝试解析为 JSON 数组 + var contentSlice []interface{} + if err := json.Unmarshal([]byte(contentStr), &contentSlice); err == nil { + // 如果是数组,包装成对象 + contentMap = map[string]interface{}{"result": contentSlice} + } else { + // 3. 如果再次失败,作为纯文本处理 + contentMap = map[string]interface{}{"content": contentStr} + } + } + functionResp := &FunctionResponse{ Name: name, Response: contentMap, diff --git a/relay/channel/mistral/text.go b/relay/channel/mistral/text.go index fb901551..e26c6101 100644 --- a/relay/channel/mistral/text.go +++ b/relay/channel/mistral/text.go @@ -47,7 +47,7 @@ func requestOpenAI2Mistral(request *dto.GeneralOpenAIRequest) *dto.GeneralOpenAI } mediaMessages := message.ParseContent() - if message.Role == "assistant" && message.ToolCalls != nil && message.Content != "null" { + if message.Role == "assistant" && message.ToolCalls != nil && message.Content == "" { mediaMessages = []dto.MediaContent{} } for j, mediaMessage := range mediaMessages { diff --git a/web/src/components/table/ChannelsTable.js b/web/src/components/table/ChannelsTable.js index ea93db32..68babe2e 100644 --- a/web/src/components/table/ChannelsTable.js +++ b/web/src/components/table/ChannelsTable.js @@ -6,9 +6,9 @@ import { showSuccess, timestamp2string, renderGroup, - renderNumberWithPoint, renderQuota, - getChannelIcon + getChannelIcon, + renderQuotaWithAmount } from '../../helpers/index.js'; import { @@ -256,7 +256,7 @@ const ChannelsTable = () => { {renderQuota(record.used_quota)} - + { prefixIcon={} onClick={() => updateChannelBalance(record)} > - ${renderNumberWithPoint(record.balance)} + {renderQuotaWithAmount(record.balance)} diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 3e3f5831..e3ef0878 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -854,7 +854,7 @@ export function renderQuotaWithAmount(amount) { if (displayInCurrency) { return '$' + amount; } else { - return renderUnitWithQuota(amount); + return renderNumber(renderUnitWithQuota(amount)); } }