Merge pull request #1920 from QuantumNous/alpha

Alpha -> main
This commit is contained in:
Calcium-Ion
2025-09-30 11:16:45 +08:00
committed by GitHub
24 changed files with 281 additions and 91 deletions
+2
View File
@@ -67,6 +67,8 @@ func ChannelType2APIType(channelType int) (int, bool) {
apiType = constant.APITypeJimeng apiType = constant.APITypeJimeng
case constant.ChannelTypeMoonshot: case constant.ChannelTypeMoonshot:
apiType = constant.APITypeMoonshot apiType = constant.APITypeMoonshot
case constant.ChannelTypeSubmodel:
apiType = constant.APITypeSubmodel
} }
if apiType == -1 { if apiType == -1 {
return constant.APITypeOpenAI, false return constant.APITypeOpenAI, false
+3 -2
View File
@@ -31,6 +31,7 @@ const (
APITypeXai APITypeXai
APITypeCoze APITypeCoze
APITypeJimeng APITypeJimeng
APITypeMoonshot // this one is only for count, do not add any channel after this APITypeMoonshot
APITypeDummy // this one is only for count, do not add any channel after this APITypeSubmodel
APITypeDummy // this one is only for count, do not add any channel after this
) )
+3
View File
@@ -50,8 +50,10 @@ const (
ChannelTypeKling = 50 ChannelTypeKling = 50
ChannelTypeJimeng = 51 ChannelTypeJimeng = 51
ChannelTypeVidu = 52 ChannelTypeVidu = 52
ChannelTypeSubmodel = 53
ChannelTypeDummy // this one is only for count, do not add any channel after this ChannelTypeDummy // this one is only for count, do not add any channel after this
) )
var ChannelBaseURLs = []string{ var ChannelBaseURLs = []string{
@@ -108,4 +110,5 @@ var ChannelBaseURLs = []string{
"https://api.klingai.com", //50 "https://api.klingai.com", //50
"https://visual.volcengineapi.com", //51 "https://visual.volcengineapi.com", //51
"https://api.vidu.cn", //52 "https://api.vidu.cn", //52
"https://llm.submodel.ai", //53
} }
+5 -4
View File
@@ -196,10 +196,11 @@ type ClaudeRequest struct {
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
//ClaudeMetadata `json:"metadata,omitempty"` //ClaudeMetadata `json:"metadata,omitempty"`
Stream bool `json:"stream,omitempty"` Stream bool `json:"stream,omitempty"`
Tools any `json:"tools,omitempty"` Tools any `json:"tools,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"` ContextManagement json.RawMessage `json:"context_management,omitempty"`
Thinking *Thinking `json:"thinking,omitempty"` ToolChoice any `json:"tool_choice,omitempty"`
Thinking *Thinking `json:"thinking,omitempty"`
} }
func (c *ClaudeRequest) GetTokenCountMeta() *types.TokenCountMeta { func (c *ClaudeRequest) GetTokenCountMeta() *types.TokenCountMeta {
+4
View File
@@ -52,6 +52,10 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
} }
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
anthropicBeta := c.Request.Header.Get("anthropic-beta")
if anthropicBeta != "" {
req.Set("anthropic-beta", anthropicBeta)
}
model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req) model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req)
return nil return nil
} }
+6
View File
@@ -16,6 +16,7 @@ var awsModelIDMap = map[string]string{
"claude-sonnet-4-20250514": "anthropic.claude-sonnet-4-20250514-v1:0", "claude-sonnet-4-20250514": "anthropic.claude-sonnet-4-20250514-v1:0",
"claude-opus-4-20250514": "anthropic.claude-opus-4-20250514-v1:0", "claude-opus-4-20250514": "anthropic.claude-opus-4-20250514-v1:0",
"claude-opus-4-1-20250805": "anthropic.claude-opus-4-1-20250805-v1:0", "claude-opus-4-1-20250805": "anthropic.claude-opus-4-1-20250805-v1:0",
"claude-sonnet-4-5-20250929": "anthropic.claude-sonnet-4-5-20250929-v1:0",
// Nova models // Nova models
"nova-micro-v1:0": "amazon.nova-micro-v1:0", "nova-micro-v1:0": "amazon.nova-micro-v1:0",
"nova-lite-v1:0": "amazon.nova-lite-v1:0", "nova-lite-v1:0": "amazon.nova-lite-v1:0",
@@ -69,6 +70,11 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{
"anthropic.claude-opus-4-1-20250805-v1:0": { "anthropic.claude-opus-4-1-20250805-v1:0": {
"us": true, "us": true,
}, },
"anthropic.claude-sonnet-4-5-20250929-v1:0": {
"us": true,
"ap": true,
"eu": true,
},
// Nova models - all support three major regions // Nova models - all support three major regions
"amazon.nova-micro-v1:0": { "amazon.nova-micro-v1:0": {
"us": true, "us": true,
+11 -2
View File
@@ -52,11 +52,16 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
} }
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
baseURL := ""
if a.RequestMode == RequestModeMessage { if a.RequestMode == RequestModeMessage {
return fmt.Sprintf("%s/v1/messages", info.ChannelBaseUrl), nil baseURL = fmt.Sprintf("%s/v1/messages", info.ChannelBaseUrl)
} else { } else {
return fmt.Sprintf("%s/v1/complete", info.ChannelBaseUrl), nil baseURL = fmt.Sprintf("%s/v1/complete", info.ChannelBaseUrl)
} }
if info.IsClaudeBetaQuery {
baseURL = baseURL + "?beta=true"
}
return baseURL, nil
} }
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
@@ -67,6 +72,10 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *rel
anthropicVersion = "2023-06-01" anthropicVersion = "2023-06-01"
} }
req.Set("anthropic-version", anthropicVersion) req.Set("anthropic-version", anthropicVersion)
anthropicBeta := c.Request.Header.Get("anthropic-beta")
if anthropicBeta != "" {
req.Set("anthropic-beta", anthropicBeta)
}
model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req) model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req)
return nil return nil
} }
+2
View File
@@ -19,6 +19,8 @@ var ModelList = []string{
"claude-opus-4-20250514-thinking", "claude-opus-4-20250514-thinking",
"claude-opus-4-1-20250805", "claude-opus-4-1-20250805",
"claude-opus-4-1-20250805-thinking", "claude-opus-4-1-20250805-thinking",
"claude-sonnet-4-5-20250929",
"claude-sonnet-4-5-20250929-thinking",
} }
var ChannelName = "claude" var ChannelName = "claude"
+86
View File
@@ -0,0 +1,86 @@
package submodel
import (
"errors"
"io"
"net/http"
"one-api/dto"
"one-api/relay/channel"
"one-api/relay/channel/openai"
relaycommon "one-api/relay/common"
"one-api/types"
"github.com/gin-gonic/gin"
)
type Adaptor struct {
}
func (a *Adaptor) ConvertGeminiRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeminiChatRequest) (any, error) {
return nil, errors.New("submodel channel: endpoint not supported")
}
func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) {
return nil, errors.New("submodel channel: endpoint not supported")
}
func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
return nil, errors.New("submodel channel: endpoint not supported")
}
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
return nil, errors.New("submodel channel: endpoint not supported")
}
func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, info.RequestURLPath, info.ChannelType), nil
}
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
channel.SetupApiRequestHeader(info, c, req)
req.Set("Authorization", "Bearer "+info.ApiKey)
return nil
}
func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
if request == nil {
return nil, errors.New("request is nil")
}
return request, nil
}
func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
return nil, errors.New("submodel channel: endpoint not supported")
}
func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
return nil, errors.New("submodel channel: endpoint not supported")
}
func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
return nil, errors.New("submodel channel: endpoint not supported")
}
func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
return channel.DoApiRequest(a, c, info, requestBody)
}
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) {
if info.IsStream {
usage, err = openai.OaiStreamHandler(c, info, resp)
} else {
usage, err = openai.OpenaiHandler(c, info, resp)
}
return
}
func (a *Adaptor) GetModelList() []string {
return ModelList
}
func (a *Adaptor) GetChannelName() string {
return ChannelName
}
+16
View File
@@ -0,0 +1,16 @@
package submodel
var ModelList = []string{
"NousResearch/Hermes-4-405B-FP8",
"Qwen/Qwen3-235B-A22B-Thinking-2507",
"Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
"Qwen/Qwen3-235B-A22B-Instruct-2507",
"zai-org/GLM-4.5-FP8",
"openai/gpt-oss-120b",
"deepseek-ai/DeepSeek-R1-0528",
"deepseek-ai/DeepSeek-R1",
"deepseek-ai/DeepSeek-V3-0324",
"deepseek-ai/DeepSeek-V3.1",
}
const ChannelName = "submodel"
+1
View File
@@ -37,6 +37,7 @@ var claudeModelMap = map[string]string{
"claude-sonnet-4-20250514": "claude-sonnet-4@20250514", "claude-sonnet-4-20250514": "claude-sonnet-4@20250514",
"claude-opus-4-20250514": "claude-opus-4@20250514", "claude-opus-4-20250514": "claude-opus-4@20250514",
"claude-opus-4-1-20250805": "claude-opus-4-1@20250805", "claude-opus-4-1-20250805": "claude-opus-4-1@20250805",
"claude-sonnet-4-5-20250929": "claude-sonnet-4-5@20250929",
} }
const anthropicVersion = "vertex-2023-10-16" const anthropicVersion = "vertex-2023-10-16"
+5 -1
View File
@@ -105,7 +105,8 @@ type RelayInfo struct {
UserQuota int UserQuota int
RelayFormat types.RelayFormat RelayFormat types.RelayFormat
SendResponseCount int SendResponseCount int
FinalPreConsumedQuota int // 最终预消耗的配额 FinalPreConsumedQuota int // 最终预消耗的配额
IsClaudeBetaQuery bool // /v1/messages?beta=true
PriceData types.PriceData PriceData types.PriceData
@@ -279,6 +280,9 @@ func GenRelayInfoClaude(c *gin.Context, request dto.Request) *RelayInfo {
info.ClaudeConvertInfo = &ClaudeConvertInfo{ info.ClaudeConvertInfo = &ClaudeConvertInfo{
LastMessagesType: LastMessageTypeNone, LastMessagesType: LastMessageTypeNone,
} }
if c.Query("beta") == "true" {
info.IsClaudeBetaQuery = true
}
return info return info
} }
+3 -1
View File
@@ -37,7 +37,7 @@ import (
"one-api/relay/channel/zhipu" "one-api/relay/channel/zhipu"
"one-api/relay/channel/zhipu_4v" "one-api/relay/channel/zhipu_4v"
"strconv" "strconv"
"one-api/relay/channel/submodel"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -103,6 +103,8 @@ func GetAdaptor(apiType int) channel.Adaptor {
return &jimeng.Adaptor{} return &jimeng.Adaptor{}
case constant.APITypeMoonshot: case constant.APITypeMoonshot:
return &moonshot.Adaptor{} // Moonshot uses Claude API return &moonshot.Adaptor{} // Moonshot uses Claude API
case constant.APITypeSubmodel:
return &submodel.Adaptor{}
} }
return nil return nil
} }
+4
View File
@@ -52,6 +52,8 @@ var defaultCacheRatio = map[string]float64{
"claude-opus-4-20250514-thinking": 0.1, "claude-opus-4-20250514-thinking": 0.1,
"claude-opus-4-1-20250805": 0.1, "claude-opus-4-1-20250805": 0.1,
"claude-opus-4-1-20250805-thinking": 0.1, "claude-opus-4-1-20250805-thinking": 0.1,
"claude-sonnet-4-5-20250929": 0.1,
"claude-sonnet-4-5-20250929-thinking": 0.1,
} }
var defaultCreateCacheRatio = map[string]float64{ var defaultCreateCacheRatio = map[string]float64{
@@ -69,6 +71,8 @@ var defaultCreateCacheRatio = map[string]float64{
"claude-opus-4-20250514-thinking": 1.25, "claude-opus-4-20250514-thinking": 1.25,
"claude-opus-4-1-20250805": 1.25, "claude-opus-4-1-20250805": 1.25,
"claude-opus-4-1-20250805-thinking": 1.25, "claude-opus-4-1-20250805-thinking": 1.25,
"claude-sonnet-4-5-20250929": 1.25,
"claude-sonnet-4-5-20250929-thinking": 1.25,
} }
//var defaultCreateCacheRatio = map[string]float64{} //var defaultCreateCacheRatio = map[string]float64{}
+12
View File
@@ -141,6 +141,7 @@ var defaultModelRatio = map[string]float64{
"claude-3-7-sonnet-20250219": 1.5, "claude-3-7-sonnet-20250219": 1.5,
"claude-3-7-sonnet-20250219-thinking": 1.5, "claude-3-7-sonnet-20250219-thinking": 1.5,
"claude-sonnet-4-20250514": 1.5, "claude-sonnet-4-20250514": 1.5,
"claude-sonnet-4-5-20250929": 1.5,
"claude-3-opus-20240229": 7.5, // $15 / 1M tokens "claude-3-opus-20240229": 7.5, // $15 / 1M tokens
"claude-opus-4-20250514": 7.5, "claude-opus-4-20250514": 7.5,
"claude-opus-4-1-20250805": 7.5, "claude-opus-4-1-20250805": 7.5,
@@ -251,6 +252,17 @@ var defaultModelRatio = map[string]float64{
"grok-vision-beta": 2.5, "grok-vision-beta": 2.5,
"grok-3-fast-beta": 2.5, "grok-3-fast-beta": 2.5,
"grok-3-mini-fast-beta": 0.3, "grok-3-mini-fast-beta": 0.3,
// submodel
"NousResearch/Hermes-4-405B-FP8": 0.8,
"Qwen/Qwen3-235B-A22B-Thinking-2507": 0.6,
"Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": 0.8,
"Qwen/Qwen3-235B-A22B-Instruct-2507": 0.3,
"zai-org/GLM-4.5-FP8": 0.8,
"openai/gpt-oss-120b": 0.5,
"deepseek-ai/DeepSeek-R1-0528": 0.8,
"deepseek-ai/DeepSeek-R1": 0.8,
"deepseek-ai/DeepSeek-V3-0324": 0.8,
"deepseek-ai/DeepSeek-V3.1": 0.8,
} }
var defaultModelPrice = map[string]float64{ var defaultModelPrice = map[string]float64{
+1 -1
View File
@@ -58,7 +58,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
loading: sidebarLoading, loading: sidebarLoading,
} = useSidebar(); } = useSidebar();
const showSkeleton = useMinimumLoadingTime(sidebarLoading); const showSkeleton = useMinimumLoadingTime(sidebarLoading, 200);
const [selectedKeys, setSelectedKeys] = useState(['home']); const [selectedKeys, setSelectedKeys] = useState(['home']);
const [chatItems, setChatItems] = useState([]); const [chatItems, setChatItems] = useState([]);
@@ -118,6 +118,9 @@ const EditTagModal = (props) => {
case 36: case 36:
localModels = ['suno_music', 'suno_lyrics']; localModels = ['suno_music', 'suno_lyrics'];
break; break;
case 53:
localModels = ['NousResearch/Hermes-4-405B-FP8', 'Qwen/Qwen3-235B-A22B-Thinking-2507', 'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8','Qwen/Qwen3-235B-A22B-Instruct-2507', 'zai-org/GLM-4.5-FP8', 'openai/gpt-oss-120b', 'deepseek-ai/DeepSeek-R1-0528', 'deepseek-ai/DeepSeek-R1', 'deepseek-ai/DeepSeek-V3-0324', 'deepseek-ai/DeepSeek-V3.1'];
break;
default: default:
localModels = getChannelModels(value); localModels = getChannelModels(value);
break; break;
+5
View File
@@ -159,6 +159,11 @@ export const CHANNEL_OPTIONS = [
color: 'purple', color: 'purple',
label: 'Vidu', label: 'Vidu',
}, },
{
value: 53,
color: 'blue',
label: 'SubModel',
},
]; ];
export const MODEL_TABLE_PAGE_SIZE = 10; export const MODEL_TABLE_PAGE_SIZE = 10;
+68 -68
View File
@@ -1200,25 +1200,25 @@ export function renderModelPrice(
const extraServices = [ const extraServices = [
webSearch && webSearchCallCount > 0 webSearch && webSearchCallCount > 0
? i18next.t( ? i18next.t(
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{ {
count: webSearchCallCount, count: webSearchCallCount,
price: webSearchPrice, price: webSearchPrice,
ratio: groupRatio, ratio: groupRatio,
ratioType: ratioLabel, ratioType: ratioLabel,
}, },
) )
: '', : '',
fileSearch && fileSearchCallCount > 0 fileSearch && fileSearchCallCount > 0
? i18next.t( ? i18next.t(
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{ {
count: fileSearchCallCount, count: fileSearchCallCount,
price: fileSearchPrice, price: fileSearchPrice,
ratio: groupRatio, ratio: groupRatio,
ratioType: ratioLabel, ratioType: ratioLabel,
}, },
) )
: '', : '',
imageGenerationCall && imageGenerationCallPrice > 0 imageGenerationCall && imageGenerationCallPrice > 0
? i18next.t( ? i18next.t(
@@ -1398,10 +1398,10 @@ export function renderAudioModelPrice(
let audioPrice = let audioPrice =
(audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio + (audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio +
(audioCompletionTokens / 1000000) * (audioCompletionTokens / 1000000) *
inputRatioPrice * inputRatioPrice *
audioRatio * audioRatio *
audioCompletionRatio * audioCompletionRatio *
groupRatio; groupRatio;
let price = textPrice + audioPrice; let price = textPrice + audioPrice;
return ( return (
<> <>
@@ -1457,27 +1457,27 @@ export function renderAudioModelPrice(
<p> <p>
{cacheTokens > 0 {cacheTokens > 0
? i18next.t( ? i18next.t(
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{ {
nonCacheInput: inputTokens - cacheTokens, nonCacheInput: inputTokens - cacheTokens,
cacheInput: cacheTokens, cacheInput: cacheTokens,
cachePrice: inputRatioPrice * cacheRatio, cachePrice: inputRatioPrice * cacheRatio,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
total: textPrice.toFixed(6), total: textPrice.toFixed(6),
}, },
) )
: i18next.t( : i18next.t(
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{ {
input: inputTokens, input: inputTokens,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
total: textPrice.toFixed(6), total: textPrice.toFixed(6),
}, },
)} )}
</p> </p>
<p> <p>
{i18next.t( {i18next.t(
@@ -1617,35 +1617,35 @@ export function renderClaudeModelPrice(
<p> <p>
{cacheTokens > 0 || cacheCreationTokens > 0 {cacheTokens > 0 || cacheCreationTokens > 0
? i18next.t( ? i18next.t(
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{ {
nonCacheInput: nonCachedTokens, nonCacheInput: nonCachedTokens,
cacheInput: cacheTokens, cacheInput: cacheTokens,
cacheRatio: cacheRatio, cacheRatio: cacheRatio,
cacheCreationInput: cacheCreationTokens, cacheCreationInput: cacheCreationTokens,
cacheCreationRatio: cacheCreationRatio, cacheCreationRatio: cacheCreationRatio,
cachePrice: cacheRatioPrice, cachePrice: cacheRatioPrice,
cacheCreationPrice: cacheCreationRatioPrice, cacheCreationPrice: cacheCreationRatioPrice,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
ratio: groupRatio, ratio: groupRatio,
ratioType: ratioLabel, ratioType: ratioLabel,
total: price.toFixed(6), total: price.toFixed(6),
}, },
) )
: i18next.t( : i18next.t(
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{ {
input: inputTokens, input: inputTokens,
price: inputRatioPrice, price: inputRatioPrice,
completion: completionTokens, completion: completionTokens,
compPrice: completionRatioPrice, compPrice: completionRatioPrice,
ratio: groupRatio, ratio: groupRatio,
ratioType: ratioLabel, ratioType: ratioLabel,
total: price.toFixed(6), total: price.toFixed(6),
}, },
)} )}
</p> </p>
<p>{i18next.t('仅供参考,以实际扣费为准')}</p> <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
</article> </article>
+1 -1
View File
@@ -40,7 +40,7 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
const location = useLocation(); const location = useLocation();
const loading = statusState?.status === undefined; const loading = statusState?.status === undefined;
const isLoading = useMinimumLoadingTime(loading); const isLoading = useMinimumLoadingTime(loading, 200);
const systemName = getSystemName(); const systemName = getSystemName();
const logo = getLogo(); const logo = getLogo();
+37 -9
View File
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact support@quantumnous.com
*/ */
import { useState, useEffect, useMemo, useContext } from 'react'; import { useState, useEffect, useMemo, useContext, useRef } from 'react';
import { StatusContext } from '../../context/Status'; import { StatusContext } from '../../context/Status';
import { API } from '../../helpers'; import { API } from '../../helpers';
@@ -29,6 +29,13 @@ export const useSidebar = () => {
const [statusState] = useContext(StatusContext); const [statusState] = useContext(StatusContext);
const [userConfig, setUserConfig] = useState(null); const [userConfig, setUserConfig] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const instanceIdRef = useRef(null);
const hasLoadedOnceRef = useRef(false);
if (!instanceIdRef.current) {
const randomPart = Math.random().toString(16).slice(2);
instanceIdRef.current = `sidebar-${Date.now()}-${randomPart}`;
}
// 默认配置 // 默认配置
const defaultAdminConfig = { const defaultAdminConfig = {
@@ -74,9 +81,17 @@ export const useSidebar = () => {
}, [statusState?.status?.SidebarModulesAdmin]); }, [statusState?.status?.SidebarModulesAdmin]);
// 加载用户配置的通用方法 // 加载用户配置的通用方法
const loadUserConfig = async () => { const loadUserConfig = async ({ withLoading } = {}) => {
const shouldShowLoader =
typeof withLoading === 'boolean'
? withLoading
: !hasLoadedOnceRef.current;
try { try {
setLoading(true); if (shouldShowLoader) {
setLoading(true);
}
const res = await API.get('/api/user/self'); const res = await API.get('/api/user/self');
if (res.data.success && res.data.data.sidebar_modules) { if (res.data.success && res.data.data.sidebar_modules) {
let config; let config;
@@ -122,18 +137,25 @@ export const useSidebar = () => {
}); });
setUserConfig(defaultUserConfig); setUserConfig(defaultUserConfig);
} finally { } finally {
setLoading(false); if (shouldShowLoader) {
setLoading(false);
}
hasLoadedOnceRef.current = true;
} }
}; };
// 刷新用户配置的方法(供外部调用) // 刷新用户配置的方法(供外部调用)
const refreshUserConfig = async () => { const refreshUserConfig = async () => {
if (Object.keys(adminConfig).length > 0) { if (Object.keys(adminConfig).length > 0) {
await loadUserConfig(); await loadUserConfig({ withLoading: false });
} }
// 触发全局刷新事件,通知所有useSidebar实例更新 // 触发全局刷新事件,通知所有useSidebar实例更新
sidebarEventTarget.dispatchEvent(new CustomEvent(SIDEBAR_REFRESH_EVENT)); sidebarEventTarget.dispatchEvent(
new CustomEvent(SIDEBAR_REFRESH_EVENT, {
detail: { sourceId: instanceIdRef.current, skipLoader: true },
}),
);
}; };
// 加载用户配置 // 加载用户配置
@@ -146,9 +168,15 @@ export const useSidebar = () => {
// 监听全局刷新事件 // 监听全局刷新事件
useEffect(() => { useEffect(() => {
const handleRefresh = () => { const handleRefresh = (event) => {
if (event?.detail?.sourceId === instanceIdRef.current) {
return;
}
if (Object.keys(adminConfig).length > 0) { if (Object.keys(adminConfig).length > 0) {
loadUserConfig(); loadUserConfig({
withLoading: event?.detail?.skipLoader ? false : undefined,
});
} }
}; };
+1
View File
@@ -1404,6 +1404,7 @@
"Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比": "Claude thinking adaptation BudgetTokens = MaxTokens * BudgetTokens percentage", "Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比": "Claude thinking adaptation BudgetTokens = MaxTokens * BudgetTokens percentage",
"思考适配 BudgetTokens 百分比": "Thinking adaptation BudgetTokens percentage", "思考适配 BudgetTokens 百分比": "Thinking adaptation BudgetTokens percentage",
"0.1-1之间的小数": "Decimal between 0.1 and 1", "0.1-1之间的小数": "Decimal between 0.1 and 1",
"0.1以上的小数": "Decimal above 0.1",
"模型相关设置": "Model related settings", "模型相关设置": "Model related settings",
"收起侧边栏": "Collapse sidebar", "收起侧边栏": "Collapse sidebar",
"展开侧边栏": "Expand sidebar", "展开侧边栏": "Expand sidebar",
+1
View File
@@ -1404,6 +1404,7 @@
"Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比": "Adaptation de la pensée Claude BudgetTokens = MaxTokens * BudgetTokens pourcentage", "Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比": "Adaptation de la pensée Claude BudgetTokens = MaxTokens * BudgetTokens pourcentage",
"思考适配 BudgetTokens 百分比": "Adaptation de la pensée BudgetTokens pourcentage", "思考适配 BudgetTokens 百分比": "Adaptation de la pensée BudgetTokens pourcentage",
"0.1-1之间的小数": "Décimal entre 0,1 et 1", "0.1-1之间的小数": "Décimal entre 0,1 et 1",
"0.1以上的小数": "Décimal supérieur à 0,1",
"模型相关设置": "Paramètres liés au modèle", "模型相关设置": "Paramètres liés au modèle",
"收起侧边栏": "Réduire la barre latérale", "收起侧边栏": "Réduire la barre latérale",
"展开侧边栏": "Développer la barre latérale", "展开侧边栏": "Développer la barre latérale",
@@ -202,9 +202,8 @@ export default function SettingClaudeModel(props) {
label={t('思考适配 BudgetTokens 百分比')} label={t('思考适配 BudgetTokens 百分比')}
field={'claude.thinking_adapter_budget_tokens_percentage'} field={'claude.thinking_adapter_budget_tokens_percentage'}
initValue={''} initValue={''}
extraText={t('0.1-1之间的小数')} extraText={t('0.1以上的小数')}
min={0.1} min={0.1}
max={1}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...inputs, ...inputs,