Merge branch 'alpha' into refactor_relay

# Conflicts:
#	dto/openai_image.go
This commit is contained in:
CaIon
2025-08-15 13:46:34 +08:00
8 changed files with 64 additions and 63 deletions

View File

@@ -9,19 +9,22 @@ import (
) )
type ImageRequest struct { type ImageRequest struct {
Model string `json:"model"` Model string `json:"model"`
Prompt string `json:"prompt" binding:"required"` Prompt string `json:"prompt" binding:"required"`
N uint `json:"n,omitempty"` N uint `json:"n,omitempty"`
Size string `json:"size,omitempty"` Size string `json:"size,omitempty"`
Quality string `json:"quality,omitempty"` Quality string `json:"quality,omitempty"`
ResponseFormat string `json:"response_format,omitempty"` ResponseFormat string `json:"response_format,omitempty"`
Style string `json:"style,omitempty"` Style json.RawMessage `json:"style,omitempty"`
User string `json:"user,omitempty"` User json.RawMessage `json:"user,omitempty"`
ExtraFields json.RawMessage `json:"extra_fields,omitempty"` ExtraFields json.RawMessage `json:"extra_fields,omitempty"`
Background string `json:"background,omitempty"` Background json.RawMessage `json:"background,omitempty"`
Moderation string `json:"moderation,omitempty"` Moderation json.RawMessage `json:"moderation,omitempty"`
OutputFormat string `json:"output_format,omitempty"` OutputFormat json.RawMessage `json:"output_format,omitempty"`
Watermark *bool `json:"watermark,omitempty"` OutputCompression json.RawMessage `json:"output_compression,omitempty"`
PartialImages json.RawMessage `json:"partial_images,omitempty"`
// Stream bool `json:"stream,omitempty"`
Watermark *bool `json:"watermark,omitempty"`
} }
func (i *ImageRequest) GetTokenCountMeta() *types.TokenCountMeta { func (i *ImageRequest) GetTokenCountMeta() *types.TokenCountMeta {

View File

@@ -197,8 +197,10 @@ func TokenAuth() func(c *gin.Context) {
// 或者是否 x-api-key 不为空且存在anthropic-version // 或者是否 x-api-key 不为空且存在anthropic-version
// 谁知道有多少不符合规范没写anthropic-version的 // 谁知道有多少不符合规范没写anthropic-version的
// 所以就这样随它去吧( // 所以就这样随它去吧(
if strings.Contains(c.Request.URL.Path, "/v1/messages") || (anthropicKey != "" && c.Request.Header.Get("anthropic-version") != "") { if strings.Contains(c.Request.URL.Path, "/v1/messages") {
c.Request.Header.Set("Authorization", "Bearer "+anthropicKey) if anthropicKey != "" {
c.Request.Header.Set("Authorization", "Bearer "+anthropicKey)
}
} }
// gemini api 从query中获取key // gemini api 从query中获取key
if strings.HasPrefix(c.Request.URL.Path, "/v1beta/models") || if strings.HasPrefix(c.Request.URL.Path, "/v1beta/models") ||

View File

@@ -35,6 +35,7 @@ const PricingTopSection = ({
models, models,
filteredModels, filteredModels,
loading, loading,
searchValue,
t t
}) => { }) => {
const [showFilterModal, setShowFilterModal] = useState(false); const [showFilterModal, setShowFilterModal] = useState(false);
@@ -46,6 +47,7 @@ const PricingTopSection = ({
<Input <Input
prefix={<IconSearch />} prefix={<IconSearch />}
placeholder={t('模糊搜索模型名称')} placeholder={t('模糊搜索模型名称')}
value={searchValue}
onCompositionStart={handleCompositionStart} onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd} onCompositionEnd={handleCompositionEnd}
onChange={handleChange} onChange={handleChange}
@@ -78,7 +80,7 @@ const PricingTopSection = ({
</Button> </Button>
)} )}
</div> </div>
), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText, isMobile]); ), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText, isMobile, searchValue]);
return ( return (
<> <>

View File

@@ -128,19 +128,6 @@ const PricingCardView = ({
return record.description || ''; return record.description || '';
}; };
// 渲染价格信息
const renderPriceInfo = (record) => {
const priceData = calculateModelPrice({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
});
return formatPriceInfo(priceData, t);
};
// 渲染标签 // 渲染标签
const renderTags = (record) => { const renderTags = (record) => {
// 计费类型标签(左边) // 计费类型标签(左边)
@@ -221,6 +208,15 @@ const PricingCardView = ({
const modelKey = getModelKey(model); const modelKey = getModelKey(model);
const isSelected = selectedRowKeys.includes(modelKey); const isSelected = selectedRowKeys.includes(modelKey);
const priceData = calculateModelPrice({
record: model,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
});
return ( return (
<Card <Card
key={modelKey || index} key={modelKey || index}
@@ -238,7 +234,7 @@ const PricingCardView = ({
{model.model_name} {model.model_name}
</h3> </h3>
<div className="flex items-center gap-3 text-xs mt-1"> <div className="flex items-center gap-3 text-xs mt-1">
{renderPriceInfo(model)} {formatPriceInfo(priceData, t)}
</div> </div>
</div> </div>
</div> </div>
@@ -313,7 +309,7 @@ const PricingCardView = ({
{t('补全')}: {model.quota_type === 0 ? parseFloat(model.completion_ratio.toFixed(3)) : t('无')} {t('补全')}: {model.quota_type === 0 ? parseFloat(model.completion_ratio.toFixed(3)) : t('无')}
</div> </div>
<div> <div>
{t('分组')}: {priceData.usedGroupRatio} {t('分组')}: {priceData?.usedGroupRatio ?? '-'}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -98,6 +98,25 @@ export const getPricingTableColumns = ({
displayPrice, displayPrice,
showRatio, showRatio,
}) => { }) => {
const priceDataCache = new WeakMap();
const getPriceData = (record) => {
let cache = priceDataCache.get(record);
if (!cache) {
cache = calculateModelPrice({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
});
priceDataCache.set(record, cache);
}
return cache;
};
const endpointColumn = { const endpointColumn = {
title: t('可用端点类型'), title: t('可用端点类型'),
dataIndex: 'supported_endpoint_types', dataIndex: 'supported_endpoint_types',
@@ -167,21 +186,21 @@ export const getPricingTableColumns = ({
dataIndex: 'model_ratio', dataIndex: 'model_ratio',
render: (text, record, index) => { render: (text, record, index) => {
const completionRatio = parseFloat(record.completion_ratio.toFixed(3)); const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
const content = ( const priceData = getPriceData(record);
return (
<div className="space-y-1"> <div className="space-y-1">
<div className="text-gray-700"> <div className="text-gray-700">
{t('模型倍率')}{record.quota_type === 0 ? text : t('无')} {t('模型倍率')}{record.quota_type === 0 ? text : t('无')}
</div> </div>
<div className="text-gray-700"> <div className="text-gray-700">
{t('补全倍率')} {t('补全倍率')}{record.quota_type === 0 ? completionRatio : t('无')}
{record.quota_type === 0 ? completionRatio : t('无')}
</div> </div>
<div className="text-gray-700"> <div className="text-gray-700">
{t('分组倍率')}{groupRatio[selectedGroup]} {t('分组倍率')}{priceData?.usedGroupRatio ?? '-'}
</div> </div>
</div> </div>
); );
return content;
}, },
}; };
@@ -190,14 +209,7 @@ export const getPricingTableColumns = ({
dataIndex: 'model_price', dataIndex: 'model_price',
fixed: 'right', fixed: 'right',
render: (text, record, index) => { render: (text, record, index) => {
const priceData = calculateModelPrice({ const priceData = getPriceData(record);
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency
});
if (priceData.isPerToken) { if (priceData.isPerToken) {
return ( return (

View File

@@ -305,6 +305,7 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
node: 'item', node: 'item',
key: i, key: i,
name, name,
value: item[name],
onClick: () => onOpenLink(name, item[name], record), onClick: () => onOpenLink(name, item[name], record),
}); });
} }
@@ -326,11 +327,8 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
if (chatsArray.length === 0) { if (chatsArray.length === 0) {
showError(t('请联系管理员配置聊天链接')); showError(t('请联系管理员配置聊天链接'));
} else { } else {
onOpenLink( const first = chatsArray[0];
'default', onOpenLink(first.name, first.value, record);
chatsArray[0].name ? (parsed => parsed)(localStorage.getItem('chats')) : '',
record,
);
} }
}} }}
> >

View File

@@ -211,9 +211,6 @@ export const useModelPricingData = () => {
}; };
const handleChange = (value) => { const handleChange = (value) => {
if (compositionRef.current.isComposition) {
return;
}
const newSearchValue = value ? value : ''; const newSearchValue = value ? value : '';
setSearchValue(newSearchValue); setSearchValue(newSearchValue);
}; };
@@ -231,9 +228,7 @@ export const useModelPricingData = () => {
const handleGroupClick = (group) => { const handleGroupClick = (group) => {
setSelectedGroup(group); setSelectedGroup(group);
// 同时将分组过滤设置为该分组
setFilterGroup(group); setFilterGroup(group);
if (group === 'all') { if (group === 'all') {
showInfo(t('已切换至最优倍率视图,每个模型使用其最低倍率分组')); showInfo(t('已切换至最优倍率视图,每个模型使用其最低倍率分组'));
} else { } else {

View File

@@ -104,34 +104,27 @@ export const usePricingFilterCounts = ({
// 生成不同视图所需的模型集合 // 生成不同视图所需的模型集合
const quotaTypeModels = useMemo( const quotaTypeModels = useMemo(
() => allModels.filter((m) => matchesFilters(m, ['quota'])), () => allModels.filter((m) => matchesFilters(m, ['quota'])),
[allModels, filterGroup, filterEndpointType, filterVendor, filterTag] [allModels, filterGroup, filterEndpointType, filterVendor, filterTag, searchValue]
); );
const endpointTypeModels = useMemo( const endpointTypeModels = useMemo(
() => allModels.filter((m) => matchesFilters(m, ['endpoint'])), () => allModels.filter((m) => matchesFilters(m, ['endpoint'])),
[allModels, filterGroup, filterQuotaType, filterVendor, filterTag] [allModels, filterGroup, filterQuotaType, filterVendor, filterTag, searchValue]
); );
const vendorModels = useMemo( const vendorModels = useMemo(
() => allModels.filter((m) => matchesFilters(m, ['vendor'])), () => allModels.filter((m) => matchesFilters(m, ['vendor'])),
[allModels, filterGroup, filterQuotaType, filterEndpointType, filterTag] [allModels, filterGroup, filterQuotaType, filterEndpointType, filterTag, searchValue]
); );
const tagModels = useMemo( const tagModels = useMemo(
() => allModels.filter((m) => matchesFilters(m, ['tag'])), () => allModels.filter((m) => matchesFilters(m, ['tag'])),
[allModels, filterGroup, filterQuotaType, filterEndpointType, filterVendor] [allModels, filterGroup, filterQuotaType, filterEndpointType, filterVendor, searchValue]
); );
const groupCountModels = useMemo( const groupCountModels = useMemo(
() => allModels.filter((m) => matchesFilters(m, ['group'])), () => allModels.filter((m) => matchesFilters(m, ['group'])),
[ [allModels, filterQuotaType, filterEndpointType, filterVendor, filterTag, searchValue]
allModels,
filterQuotaType,
filterEndpointType,
filterVendor,
filterTag,
searchValue,
]
); );
return { return {