Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ab65a8221 | |||
| 7cfaf6c335 | |||
| 2bedd31b42 | |||
| c20060931b | |||
| 8b22161527 |
@@ -160,7 +160,7 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
|
||||
Type: "adaptive",
|
||||
}
|
||||
claudeRequest.OutputConfig = json.RawMessage(fmt.Sprintf(`{"effort":"%s"}`, effortLevel))
|
||||
claudeRequest.TopP = common.GetPointer[float64](0)
|
||||
claudeRequest.TopP = nil
|
||||
claudeRequest.Temperature = common.GetPointer[float64](1.0)
|
||||
} else if model_setting.GetClaudeSettings().ThinkingAdapterEnabled &&
|
||||
strings.HasSuffix(textRequest.Model, "-thinking") {
|
||||
|
||||
@@ -136,8 +136,8 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||
task = "chat/completions" + task
|
||||
}
|
||||
|
||||
// 特殊处理 responses API
|
||||
if info.RelayMode == relayconstant.RelayModeResponses {
|
||||
// 特殊处理 responses API(包含 compact)
|
||||
if info.RelayMode == relayconstant.RelayModeResponses || info.RelayMode == relayconstant.RelayModeResponsesCompact {
|
||||
responsesApiVersion := "preview"
|
||||
|
||||
subUrl := "/openai/v1/responses"
|
||||
@@ -150,6 +150,11 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||
responsesApiVersion = info.ChannelOtherSettings.AzureResponsesVersion
|
||||
}
|
||||
|
||||
// compact 模式追加 /compact
|
||||
if info.RelayMode == relayconstant.RelayModeResponsesCompact {
|
||||
subUrl = subUrl + "/compact"
|
||||
}
|
||||
|
||||
requestURL = fmt.Sprintf("%s?api-version=%s", subUrl, responsesApiVersion)
|
||||
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, requestURL, info.ChannelType), nil
|
||||
}
|
||||
|
||||
@@ -442,6 +442,14 @@ const SubscriptionPlansCard = ({
|
||||
(subscription?.end_time || 0) * 1000,
|
||||
).toLocaleString()}
|
||||
</div>
|
||||
{isActive && subscription?.next_reset_time > 0 && (
|
||||
<div className='text-xs text-gray-500 mb-2'>
|
||||
{t('下一次重置')}:{' '}
|
||||
{new Date(
|
||||
subscription.next_reset_time * 1000,
|
||||
).toLocaleString()}
|
||||
</div>
|
||||
)}
|
||||
<div className='text-xs text-gray-500 mb-2'>
|
||||
{t('总额度')}:{' '}
|
||||
{totalAmount > 0 ? (
|
||||
|
||||
+57
-1
@@ -214,6 +214,29 @@ export const useDashboardCharts = (
|
||||
},
|
||||
],
|
||||
},
|
||||
dimension: {
|
||||
content: [
|
||||
{
|
||||
key: (datum) => datum['Model'],
|
||||
value: (datum) => datum['Count'] || 0,
|
||||
},
|
||||
],
|
||||
updateContent: (array) => {
|
||||
array.sort((a, b) => b.value - a.value);
|
||||
let sum = 0;
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
let value = parseFloat(array[i].value);
|
||||
if (isNaN(value)) value = 0;
|
||||
sum += value;
|
||||
array[i].value = renderNumber(value);
|
||||
}
|
||||
array.unshift({
|
||||
key: t('总计'),
|
||||
value: renderNumber(sum),
|
||||
});
|
||||
return array;
|
||||
},
|
||||
},
|
||||
},
|
||||
color: {
|
||||
specified: modelColorMap,
|
||||
@@ -335,6 +358,27 @@ export const useDashboardCharts = (
|
||||
value: (datum) => renderQuota(datum['rawQuota'] || 0, 4),
|
||||
}],
|
||||
},
|
||||
dimension: {
|
||||
content: [{
|
||||
key: (datum) => datum['User'],
|
||||
value: (datum) => datum['rawQuota'] || 0,
|
||||
}],
|
||||
updateContent: (array) => {
|
||||
array.sort((a, b) => b.value - a.value);
|
||||
let sum = 0;
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
let value = parseFloat(array[i].value);
|
||||
if (isNaN(value)) value = 0;
|
||||
sum += value;
|
||||
array[i].value = renderQuota(value, 4);
|
||||
}
|
||||
array.unshift({
|
||||
key: t('总计'),
|
||||
value: renderQuota(sum, 4),
|
||||
});
|
||||
return array;
|
||||
},
|
||||
},
|
||||
},
|
||||
color: { type: 'ordinal', range: USER_COLORS },
|
||||
});
|
||||
@@ -463,13 +507,25 @@ export const useDashboardCharts = (
|
||||
modelLineData.sort((a, b) => a.Time.localeCompare(b.Time));
|
||||
|
||||
// ===== 模型调用次数排行柱状图 =====
|
||||
const rankData = Array.from(modelTotals)
|
||||
const MAX_RANK_MODELS = 20;
|
||||
const allRankData = Array.from(modelTotals)
|
||||
.map(([model, count]) => ({
|
||||
Model: model,
|
||||
Count: count,
|
||||
}))
|
||||
.sort((a, b) => b.Count - a.Count);
|
||||
|
||||
let rankData;
|
||||
if (allRankData.length > MAX_RANK_MODELS) {
|
||||
const topModels = allRankData.slice(0, MAX_RANK_MODELS);
|
||||
const otherCount = allRankData
|
||||
.slice(MAX_RANK_MODELS)
|
||||
.reduce((sum, item) => sum + item.Count, 0);
|
||||
rankData = [...topModels, { Model: t('其他'), Count: otherCount }];
|
||||
} else {
|
||||
rankData = allRankData;
|
||||
}
|
||||
|
||||
updateChartSpec(
|
||||
setSpecModelLine,
|
||||
modelLineData,
|
||||
|
||||
Vendored
+1
@@ -440,6 +440,7 @@
|
||||
"余额充值管理": "Balance recharge management",
|
||||
"作废": "Invalidate",
|
||||
"作废于": "Invalidated at",
|
||||
"下一次重置": "Next reset",
|
||||
"作废后该订阅将立即失效,历史记录不受影响。是否继续?": "After invalidation, the subscription becomes invalid immediately. History is not affected. Continue?",
|
||||
"作用域": "Scope",
|
||||
"作用域:包含分组": "Scope: Include Group",
|
||||
|
||||
Vendored
+1
@@ -435,6 +435,7 @@
|
||||
"余额充值管理": "Recharge du solde",
|
||||
"作废": "Invalider",
|
||||
"作废于": "Invalidé le",
|
||||
"下一次重置": "Prochaine réinitialisation",
|
||||
"作废后该订阅将立即失效,历史记录不受影响。是否继续?": "Après invalidation, l'abonnement devient immédiatement invalide. L'historique n'est pas affecté. Continuer ?",
|
||||
"作用域": "Portée",
|
||||
"作用域:包含分组": "Portée : inclure le groupe",
|
||||
|
||||
Vendored
+1
@@ -431,6 +431,7 @@
|
||||
"余额充值管理": "残高チャージ管理",
|
||||
"作废": "無効化",
|
||||
"作废于": "無効化日",
|
||||
"下一次重置": "次回リセット",
|
||||
"作废后该订阅将立即失效,历史记录不受影响。是否继续?": "無効化するとこのサブスクリプションは直ちに失効します。履歴には影響しません。続行しますか?",
|
||||
"作用域": "スコープ",
|
||||
"作用域:包含分组": "スコープ:グループを含む",
|
||||
|
||||
Vendored
+1
@@ -438,6 +438,7 @@
|
||||
"余额充值管理": "Управление пополнением баланса",
|
||||
"作废": "Аннулировать",
|
||||
"作废于": "Аннулировано",
|
||||
"下一次重置": "Следующий сброс",
|
||||
"作废后该订阅将立即失效,历史记录不受影响。是否继续?": "После аннулирования подписка сразу станет недействительной. История не изменится. Продолжить?",
|
||||
"作用域": "Область действия",
|
||||
"作用域:包含分组": "Область действия: включить группу",
|
||||
|
||||
Vendored
+1
@@ -432,6 +432,7 @@
|
||||
"余额充值管理": "Quản lý nạp tiền số dư",
|
||||
"作废": "Vô hiệu",
|
||||
"作废于": "Vô hiệu vào",
|
||||
"下一次重置": "Đặt lại tiếp theo",
|
||||
"作废后该订阅将立即失效,历史记录不受影响。是否继续?": "Sau khi vô hiệu, đăng ký sẽ mất hiệu lực ngay. Lịch sử không bị ảnh hưởng. Tiếp tục?",
|
||||
"作用域": "Phạm vi",
|
||||
"作用域:包含分组": "Phạm vi: Bao gồm nhóm",
|
||||
|
||||
Vendored
+1
@@ -2797,6 +2797,7 @@
|
||||
"至": "至",
|
||||
"过期于": "过期于",
|
||||
"作废于": "作废于",
|
||||
"下一次重置": "下一次重置",
|
||||
"购买套餐后即可享受模型权益": "购买套餐后即可享受模型权益",
|
||||
"限购": "限购",
|
||||
"推荐": "推荐",
|
||||
|
||||
Vendored
+1
@@ -379,6 +379,7 @@
|
||||
"余额充值管理": "餘額儲值管理",
|
||||
"作废": "作廢",
|
||||
"作废于": "作廢於",
|
||||
"下一次重置": "下一次重置",
|
||||
"作废后该订阅将立即失效,历史记录不受影响。是否继续?": "作廢後該訂閱將立即失效,歷史記錄不受影響。是否繼續?",
|
||||
"你似乎并没有修改什么": "你似乎並沒有修改什麼",
|
||||
"你可以在“自定义模型名称”处手动添加它们,然后点击填入后再提交,或者直接使用下方操作自动处理。": "你可以在「自訂模型名稱」處手動添加它們,然後點擊填入後再提交,或者直接使用下方操作自動處理。",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import React, { useState, useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
@@ -61,60 +61,63 @@ export function serializeGroupTable(rows) {
|
||||
};
|
||||
}
|
||||
|
||||
export default function GroupTable({
|
||||
groupRatio,
|
||||
userUsableGroups,
|
||||
onChange,
|
||||
}) {
|
||||
export default function GroupTable({ groupRatio, userUsableGroups, onChange }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [rows, setRows] = useState(() =>
|
||||
buildRows(groupRatio, userUsableGroups),
|
||||
);
|
||||
|
||||
const emitChange = useCallback(
|
||||
(newRows) => {
|
||||
setRows(newRows);
|
||||
onChange?.(serializeGroupTable(newRows));
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
// Use functional setRows to keep updateRow/addRow/removeRow referentially
|
||||
// stable, preventing columns useMemo from rebuilding on every keystroke
|
||||
// which causes the Input cursor to jump to end (cursor reset bug).
|
||||
const onChangeRef = useRef(onChange);
|
||||
onChangeRef.current = onChange;
|
||||
|
||||
const emitAndSet = useCallback((updater) => {
|
||||
setRows((prev) => {
|
||||
const next = typeof updater === 'function' ? updater(prev) : updater;
|
||||
onChangeRef.current?.(serializeGroupTable(next));
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateRow = useCallback(
|
||||
(id, field, value) => {
|
||||
const next = rows.map((r) =>
|
||||
r._id === id ? { ...r, [field]: value } : r,
|
||||
emitAndSet((prev) =>
|
||||
prev.map((r) => (r._id === id ? { ...r, [field]: value } : r)),
|
||||
);
|
||||
emitChange(next);
|
||||
},
|
||||
[rows, emitChange],
|
||||
[emitAndSet],
|
||||
);
|
||||
|
||||
const addRow = useCallback(() => {
|
||||
const existingNames = new Set(rows.map((r) => r.name));
|
||||
let counter = 1;
|
||||
let newName = `group_${counter}`;
|
||||
while (existingNames.has(newName)) {
|
||||
counter++;
|
||||
newName = `group_${counter}`;
|
||||
}
|
||||
emitChange([
|
||||
...rows,
|
||||
{
|
||||
_id: uid(),
|
||||
name: newName,
|
||||
ratio: 1,
|
||||
selectable: true,
|
||||
description: '',
|
||||
},
|
||||
]);
|
||||
}, [rows, emitChange]);
|
||||
emitAndSet((prev) => {
|
||||
const existingNames = new Set(prev.map((r) => r.name));
|
||||
let counter = 1;
|
||||
let newName = `group_${counter}`;
|
||||
while (existingNames.has(newName)) {
|
||||
counter++;
|
||||
newName = `group_${counter}`;
|
||||
}
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
_id: uid(),
|
||||
name: newName,
|
||||
ratio: 1,
|
||||
selectable: true,
|
||||
description: '',
|
||||
},
|
||||
];
|
||||
});
|
||||
}, [emitAndSet]);
|
||||
|
||||
const removeRow = useCallback(
|
||||
(id) => {
|
||||
emitChange(rows.filter((r) => r._id !== id));
|
||||
emitAndSet((prev) => prev.filter((r) => r._id !== id));
|
||||
},
|
||||
[rows, emitChange],
|
||||
[emitAndSet],
|
||||
);
|
||||
|
||||
const groupNames = useMemo(() => rows.map((r) => r.name), [rows]);
|
||||
@@ -127,6 +130,11 @@ export default function GroupTable({
|
||||
return new Set(Object.keys(counts).filter((k) => counts[k] > 1));
|
||||
}, [groupNames]);
|
||||
|
||||
// Use ref so column render functions always read the latest duplicate set
|
||||
// without adding duplicateNames to columns deps (which would break cursor).
|
||||
const duplicateNamesRef = useRef(duplicateNames);
|
||||
duplicateNamesRef.current = duplicateNames;
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -138,7 +146,9 @@ export default function GroupTable({
|
||||
<Input
|
||||
size='small'
|
||||
value={record.name}
|
||||
status={duplicateNames.has(record.name) ? 'warning' : undefined}
|
||||
status={
|
||||
duplicateNamesRef.current.has(record.name) ? 'warning' : undefined
|
||||
}
|
||||
onChange={(v) => updateRow(record._id, 'name', v)}
|
||||
/>
|
||||
),
|
||||
@@ -212,7 +222,7 @@ export default function GroupTable({
|
||||
),
|
||||
},
|
||||
],
|
||||
[t, duplicateNames, updateRow, removeRow],
|
||||
[t, updateRow, removeRow],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -223,9 +233,7 @@ export default function GroupTable({
|
||||
rowKey='_id'
|
||||
hidePagination
|
||||
size='small'
|
||||
empty={
|
||||
<Text type='tertiary'>{t('暂无分组,点击下方按钮添加')}</Text>
|
||||
}
|
||||
empty={<Text type='tertiary'>{t('暂无分组,点击下方按钮添加')}</Text>}
|
||||
/>
|
||||
<div className='mt-3 flex justify-center'>
|
||||
<Button icon={<IconPlus />} theme='outline' onClick={addRow}>
|
||||
@@ -234,7 +242,8 @@ export default function GroupTable({
|
||||
</div>
|
||||
{duplicateNames.size > 0 && (
|
||||
<Text type='warning' size='small' className='mt-2 block'>
|
||||
{t('存在重复的分组名称:')}{Array.from(duplicateNames).join(', ')}
|
||||
{t('存在重复的分组名称:')}
|
||||
{Array.from(duplicateNames).join(', ')}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user