- Introduced a new ModelConfigEditor component for managing model configurations. - Added API endpoints for model configuration management, including fetching, saving, and importing/exporting configurations. - Enhanced logging for request bodies, particularly for multimodal requests containing image URLs. - Updated the .gitignore to include new configuration files. - Refactored model handling in the controller to support custom model configurations.
154 lines
4.4 KiB
Go
154 lines
4.4 KiB
Go
package model
|
|
|
|
import (
|
|
"github.com/songquanpeng/one-api/common/logger"
|
|
)
|
|
|
|
type Message struct {
|
|
Role string `json:"role,omitempty"`
|
|
Content any `json:"content,omitempty"`
|
|
ReasoningContent any `json:"reasoning_content,omitempty"`
|
|
ReasoningEncryptedContent string `json:"reasoning_encrypted_content,omitempty"`
|
|
Name *string `json:"name,omitempty"`
|
|
ToolCalls []Tool `json:"tool_calls,omitempty"`
|
|
ToolCallId string `json:"tool_call_id,omitempty"`
|
|
}
|
|
|
|
func (m Message) IsStringContent() bool {
|
|
_, ok := m.Content.(string)
|
|
return ok
|
|
}
|
|
|
|
func (m Message) StringContent() string {
|
|
content, ok := m.Content.(string)
|
|
if ok {
|
|
return content
|
|
}
|
|
contentList, ok := m.Content.([]any)
|
|
if ok {
|
|
var contentStr string
|
|
for _, contentItem := range contentList {
|
|
contentMap, ok := contentItem.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if contentMap["type"] == ContentTypeText {
|
|
if subStr, ok := contentMap["text"].(string); ok {
|
|
contentStr += subStr
|
|
}
|
|
}
|
|
}
|
|
return contentStr
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m Message) ParseContent() []MessageContent {
|
|
var contentList []MessageContent
|
|
content, ok := m.Content.(string)
|
|
if ok {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeText,
|
|
Text: content,
|
|
})
|
|
return contentList
|
|
}
|
|
anyList, ok := m.Content.([]any)
|
|
if ok {
|
|
logger.Debugf(nil, "=== ParseContent: Found array content with %d items ===", len(anyList))
|
|
for i, contentItem := range anyList {
|
|
contentMap, ok := contentItem.(map[string]any)
|
|
if !ok {
|
|
logger.Debugf(nil, "=== ParseContent: Item %d is not a map, type=%T ===", i, contentItem)
|
|
continue
|
|
}
|
|
logger.Debugf(nil, "=== ParseContent: Item %d has type=%v ===", i, contentMap["type"])
|
|
switch contentMap["type"] {
|
|
case ContentTypeText:
|
|
if subStr, ok := contentMap["text"].(string); ok {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeText,
|
|
Text: subStr,
|
|
})
|
|
}
|
|
case ContentTypeImageURL:
|
|
logger.Debugf(nil, "=== ParseContent: Processing image_url item ===")
|
|
if subObj, ok := contentMap["image_url"].(map[string]any); ok {
|
|
if url, ok := subObj["url"].(string); ok {
|
|
logger.Debugf(nil, "=== ParseContent: Found image URL: %s ===", truncateURL(url))
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeImageURL,
|
|
ImageURL: &ImageURL{
|
|
Url: url,
|
|
},
|
|
})
|
|
} else {
|
|
logger.Errorf(nil, "=== ParseContent: image_url.url is not a string, type=%T ===", subObj["url"])
|
|
}
|
|
} else {
|
|
logger.Errorf(nil, "=== ParseContent: image_url is not a map, type=%T ===", contentMap["image_url"])
|
|
}
|
|
case ContentTypeVideoURL:
|
|
if subObj, ok := contentMap["video_url"].(map[string]any); ok {
|
|
if url, ok := subObj["url"].(string); ok {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeVideoURL,
|
|
VideoURL: &VideoURL{Url: url},
|
|
})
|
|
}
|
|
}
|
|
case ContentTypeInputAudio:
|
|
if subObj, ok := contentMap["input_audio"].(map[string]any); ok {
|
|
data, _ := subObj["data"].(string)
|
|
format, _ := subObj["format"].(string)
|
|
if data != "" {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeInputAudio,
|
|
InputAudio: &InputAudio{Data: data, Format: format},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
imageCount := 0
|
|
for _, c := range contentList {
|
|
if c.Type == ContentTypeImageURL {
|
|
imageCount++
|
|
}
|
|
}
|
|
logger.Debugf(nil, "=== ParseContent: Parsed %d content items, image_count=%d ===", len(contentList), imageCount)
|
|
return contentList
|
|
}
|
|
logger.Debugf(nil, "=== ParseContent: Content is neither string nor array, type=%T ===", m.Content)
|
|
return nil
|
|
}
|
|
|
|
func truncateURL(url string) string {
|
|
if len(url) > 100 {
|
|
return url[:100] + "..."
|
|
}
|
|
return url
|
|
}
|
|
|
|
type ImageURL struct {
|
|
Url string `json:"url,omitempty"`
|
|
Detail string `json:"detail,omitempty"`
|
|
}
|
|
|
|
type VideoURL struct {
|
|
Url string `json:"url,omitempty"`
|
|
}
|
|
|
|
type InputAudio struct {
|
|
Data string `json:"data,omitempty"` // base64-encoded audio (no data: prefix)
|
|
Format string `json:"format,omitempty"` // e.g. "mp3", "wav", "webm", "ogg"
|
|
}
|
|
|
|
type MessageContent struct {
|
|
Type string `json:"type,omitempty"`
|
|
Text string `json:"text"`
|
|
ImageURL *ImageURL `json:"image_url,omitempty"`
|
|
VideoURL *VideoURL `json:"video_url,omitempty"`
|
|
InputAudio *InputAudio `json:"input_audio,omitempty"`
|
|
}
|