Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bc322dd
refactor: optimize Dockerfile apk usage
PeterDaveHello Apr 29, 2025
4a313a5
feat: add moonshot(kimi) update balance
feitianbubu Jun 5, 2025
1ec2bbd
update input range and description for thinking adapter budget tokens
RedwindA Jun 12, 2025
a39b2f5
feat(ratio): add new Gemini model ratios and enhance flash model hand…
RedwindA Jun 17, 2025
f35784a
feat(gemini): update audio input pricing and adjust model handling logic
RedwindA Jun 17, 2025
b95c5bb
feat(gemini): add pricing for Gemini 2.5 Flash Lite preview audio input
RedwindA Jun 17, 2025
098880b
Merge remote-tracking branch 'upstream/alpha' into update-gemini-ratio
RedwindA Jun 19, 2025
c192d07
fix gizmo completion ratio
RedwindA Jun 19, 2025
0b326e7
🌐 feat(i18n): add English translation for "暴露倍率接口"
zeraturing Jun 20, 2025
9c3a13c
📝 i18n: Update ratio sync message text
zeraturing Jun 20, 2025
edaff1c
🎨 feat(UI): Add Lucide icons to settings tabs for improved navigation
t0ng7u Jun 20, 2025
8ec18dd
💬 refactor: separate chat settings into dedicated tab
zeraturing Jun 20, 2025
18417ba
🎨 refactor(UI): Move drawing settings to a separate tab
zeraturing Jun 20, 2025
c3d2d07
🚚 refactor: Move DataDashboard settings from Operation to Dashboard s…
zeraturing Jun 20, 2025
01ef1fe
📝 refactor: reorganize payment settings into dedicated tab
t0ng7u Jun 20, 2025
2902d6c
🎨 style: add `mt-2` style in ratio section
t0ng7u Jun 20, 2025
4ebf9e3
Merge pull request #1248 from RedwindA/update-gemini-ratio
Calcium-Ion Jun 20, 2025
8d66828
Merge pull request #1210 from RedwindA/feat/128-budget
Calcium-Ion Jun 20, 2025
ccda142
Merge pull request #1135 from PeterDaveHelloKitchen/Dockerfile
Calcium-Ion Jun 20, 2025
fcaa831
Merge pull request #1160 from feitianbubu/add-moonshot-kimi-update-ba…
Calcium-Ion Jun 20, 2025
1ad2f63
✨ feat: Enhance announcements UX with unread badge, tabbed NoticeModa…
zeraturing Jun 20, 2025
83a3872
Merge remote-tracking branch 'origin/alpha' into alpha
zeraturing Jun 20, 2025
d487be0
✨ feat(settings/announcements): sort by publishDate desc
t0ng7u Jun 20, 2025
50b4fc0
Merge branch 'main' into alpha
Calcium-Ion Jun 21, 2025
6bb5521
✨ feat(relay-gemini): conditionally set ThinkingBudget based on MaxOu…
Calcium-Ion Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ RUN go build -ldflags "-s -w -X 'one-api/common.Version=$(cat VERSION)'" -o one-

FROM alpine

RUN apk update \
&& apk upgrade \
RUN apk upgrade --no-cache \
&& apk add --no-cache ca-certificates tzdata ffmpeg \
&& update-ca-certificates

Expand Down
38 changes: 38 additions & 0 deletions controller/channel-billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/shopspring/decimal"
"io"
"net/http"
"one-api/common"
"one-api/model"
"one-api/service"
"one-api/setting"
"strconv"
"time"

Expand Down Expand Up @@ -304,6 +306,40 @@ func updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {
return balance, nil
}

func updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {
url := "https://api.moonshot.cn/v1/users/me/balance"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}

type MoonshotBalanceData struct {
AvailableBalance float64 `json:"available_balance"`
VoucherBalance float64 `json:"voucher_balance"`
CashBalance float64 `json:"cash_balance"`
}

type MoonshotBalanceResponse struct {
Code int `json:"code"`
Data MoonshotBalanceData `json:"data"`
Scode string `json:"scode"`
Status bool `json:"status"`
}

response := MoonshotBalanceResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
if !response.Status || response.Code != 0 {
return 0, fmt.Errorf("failed to update moonshot balance, status: %v, code: %d, scode: %s", response.Status, response.Code, response.Scode)
}
availableBalanceCny := response.Data.AvailableBalance
availableBalanceUsd := decimal.NewFromFloat(availableBalanceCny).Div(decimal.NewFromFloat(setting.Price)).InexactFloat64()
channel.UpdateBalance(availableBalanceUsd)
return availableBalanceUsd, nil
}

func updateChannelBalance(channel *model.Channel) (float64, error) {
baseURL := common.ChannelBaseURLs[channel.Type]
if channel.GetBaseURL() == "" {
Expand Down Expand Up @@ -332,6 +368,8 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
return updateChannelDeepSeekBalance(channel)
case common.ChannelTypeOpenRouter:
return updateChannelOpenRouterBalance(channel)
case common.ChannelTypeMoonshot:
return updateChannelMoonshotBalance(channel)
default:
return 0, errors.New("尚未实现")
}
Expand Down
11 changes: 6 additions & 5 deletions relay/channel/gemini/relay-gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon
isNew25Pro := strings.HasPrefix(modelName, "gemini-2.5-pro") &&
!strings.HasPrefix(modelName, "gemini-2.5-pro-preview-05-06") &&
!strings.HasPrefix(modelName, "gemini-2.5-pro-preview-03-25")
is25FlashLite := strings.HasPrefix(modelName, "gemini-2.5-flash-lite")

if strings.Contains(modelName, "-thinking-") {
parts := strings.SplitN(modelName, "-thinking-", 2)
Expand Down Expand Up @@ -134,15 +133,17 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon
IncludeThoughts: true,
}
} else {
budgetTokens := model_setting.GetGeminiSettings().ThinkingAdapterBudgetTokensPercentage * float64(geminiRequest.GenerationConfig.MaxOutputTokens)
clampedBudget := clampThinkingBudget(modelName, int(budgetTokens))
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
ThinkingBudget: common.GetPointer(clampedBudget),
IncludeThoughts: true,
}
if geminiRequest.GenerationConfig.MaxOutputTokens > 0 {
budgetTokens := model_setting.GetGeminiSettings().ThinkingAdapterBudgetTokensPercentage * float64(geminiRequest.GenerationConfig.MaxOutputTokens)
clampedBudget := clampThinkingBudget(modelName, int(budgetTokens))
geminiRequest.GenerationConfig.ThinkingConfig.ThinkingBudget = common.GetPointer(clampedBudget)
}
}
} else if strings.HasSuffix(modelName, "-nothinking") {
if !isNew25Pro && !is25FlashLite {
if !isNew25Pro {
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
ThinkingBudget: common.GetPointer(0),
}
Expand Down
12 changes: 9 additions & 3 deletions setting/operation_setting/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const (
const (
// Gemini Audio Input Price
Gemini25FlashPreviewInputAudioPrice = 1.00
Gemini25FlashProductionInputAudioPrice = 1.00 // for `gemini-2.5-flash`
Gemini25FlashLitePreviewInputAudioPrice = 0.50
Gemini25FlashNativeAudioInputAudioPrice = 3.00
Gemini20FlashInputAudioPrice = 0.70
)
Expand Down Expand Up @@ -64,10 +66,14 @@ func GetFileSearchPricePerThousand() float64 {
}

func GetGeminiInputAudioPricePerMillionTokens(modelName string) float64 {
if strings.HasPrefix(modelName, "gemini-2.5-flash-preview") {
return Gemini25FlashPreviewInputAudioPrice
} else if strings.HasPrefix(modelName, "gemini-2.5-flash-preview-native-audio") {
if strings.HasPrefix(modelName, "gemini-2.5-flash-preview-native-audio") {
return Gemini25FlashNativeAudioInputAudioPrice
} else if strings.HasPrefix(modelName, "gemini-2.5-flash-preview-lite") {
return Gemini25FlashLitePreviewInputAudioPrice
} else if strings.HasPrefix(modelName, "gemini-2.5-flash-preview") {
return Gemini25FlashPreviewInputAudioPrice
} else if strings.HasPrefix(modelName, "gemini-2.5-flash") {
return Gemini25FlashProductionInputAudioPrice
} else if strings.HasPrefix(modelName, "gemini-2.0-flash") {
return Gemini20FlashInputAudioPrice
}
Expand Down
31 changes: 19 additions & 12 deletions setting/ratio_setting/model_ratio.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ var defaultModelRatio = map[string]float64{
"gemini-2.0-flash": 0.05,
"gemini-2.5-pro-exp-03-25": 0.625,
"gemini-2.5-pro-preview-03-25": 0.625,
"gemini-2.5-pro": 0.625,
"gemini-2.5-flash-preview-04-17": 0.075,
"gemini-2.5-flash-preview-04-17-thinking": 0.075,
"gemini-2.5-flash-preview-04-17-nothinking": 0.075,
Expand All @@ -148,6 +149,8 @@ var defaultModelRatio = map[string]float64{
"gemini-2.5-flash-preview-05-20-nothinking": 0.075,
"gemini-2.5-flash-thinking-*": 0.075, // 用于为后续所有2.5 flash thinking budget 模型设置默认倍率
"gemini-2.5-pro-thinking-*": 0.625, // 用于为后续所有2.5 pro thinking budget 模型设置默认倍率
"gemini-2.5-flash-lite-preview-06-17": 0.05,
"gemini-2.5-flash": 0.15,
"text-embedding-004": 0.001,
"chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
"chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
Expand Down Expand Up @@ -423,7 +426,12 @@ func UpdateCompletionRatioByJSONString(jsonStr string) error {
func GetCompletionRatio(name string) float64 {
CompletionRatioMutex.RLock()
defer CompletionRatioMutex.RUnlock()

if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
if strings.HasPrefix(name, "gpt-4o-gizmo") {
name = "gpt-4o-gizmo-*"
}
if strings.Contains(name, "/") {
if ratio, ok := CompletionRatio[name]; ok {
return ratio
Expand All @@ -441,12 +449,6 @@ func GetCompletionRatio(name string) float64 {

func getHardcodedCompletionModelRatio(name string) (float64, bool) {
lowercaseName := strings.ToLower(name)
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
if strings.HasPrefix(name, "gpt-4o-gizmo") {
name = "gpt-4o-gizmo-*"
}
if strings.HasPrefix(name, "gpt-4") && !strings.HasSuffix(name, "-all") && !strings.HasSuffix(name, "-gizmo-*") {
if strings.HasPrefix(name, "gpt-4o") {
if name == "gpt-4o-2024-05-13" {
Expand Down Expand Up @@ -500,12 +502,17 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) {
return 4, true
} else if strings.HasPrefix(name, "gemini-2.5-pro") { // 移除preview来增加兼容性,这里假设正式版的倍率和preview一致
return 8, true
} else if strings.HasPrefix(name, "gemini-2.5-flash") { // 同上
if strings.HasSuffix(name, "-nothinking") {
return 4, false
} else {
return 3.5 / 0.6, false
} else if strings.HasPrefix(name, "gemini-2.5-flash") { // 处理不同的flash模型倍率
if strings.HasPrefix(name, "gemini-2.5-flash-preview") {
if strings.HasSuffix(name, "-nothinking") {
return 4, true
}
return 3.5 / 0.15, true
}
if strings.HasPrefix(name, "gemini-2.5-flash-lite-preview") {
return 4, true
}
return 2.5 / 0.3, true
}
return 4, false
}
Expand Down
92 changes: 80 additions & 12 deletions web/src/components/layout/HeaderBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Tag,
Typography,
Skeleton,
Badge,
} from '@douyinfe/semi-ui';
import { StatusContext } from '../../context/Status/index.js';
import { useStyle, styleActions } from '../../context/Style/index.js';
Expand All @@ -43,6 +44,7 @@ const HeaderBar = () => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const location = useLocation();
const [noticeVisible, setNoticeVisible] = useState(false);
const [unreadCount, setUnreadCount] = useState(0);

const systemName = getSystemName();
const logo = getLogo();
Expand All @@ -53,9 +55,44 @@ const HeaderBar = () => {
const docsLink = statusState?.status?.docs_link || '';
const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;

const isConsoleRoute = location.pathname.startsWith('/console');

const theme = useTheme();
const setTheme = useSetTheme();

const announcements = statusState?.status?.announcements || [];

const getAnnouncementKey = (a) => `${a?.publishDate || ''}-${(a?.content || '').slice(0, 30)}`;

const calculateUnreadCount = () => {
if (!announcements.length) return 0;
let readKeys = [];
try {
readKeys = JSON.parse(localStorage.getItem('notice_read_keys')) || [];
} catch (_) {
readKeys = [];
}
const readSet = new Set(readKeys);
return announcements.filter((a) => !readSet.has(getAnnouncementKey(a))).length;
};

const getUnreadKeys = () => {
if (!announcements.length) return [];
let readKeys = [];
try {
readKeys = JSON.parse(localStorage.getItem('notice_read_keys')) || [];
} catch (_) {
readKeys = [];
}
const readSet = new Set(readKeys);
return announcements.filter((a) => !readSet.has(getAnnouncementKey(a))).map(getAnnouncementKey);
};

useEffect(() => {
setUnreadCount(calculateUnreadCount());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [announcements]);

const mainNavLinks = [
{
text: t('首页'),
Expand Down Expand Up @@ -106,6 +143,25 @@ const HeaderBar = () => {
}, 3000);
};

const handleNoticeOpen = () => {
setNoticeVisible(true);
};

const handleNoticeClose = () => {
setNoticeVisible(false);
if (announcements.length) {
let readKeys = [];
try {
readKeys = JSON.parse(localStorage.getItem('notice_read_keys')) || [];
} catch (_) {
readKeys = [];
}
const mergedKeys = Array.from(new Set([...readKeys, ...announcements.map(getAnnouncementKey)]));
localStorage.setItem('notice_read_keys', JSON.stringify(mergedKeys));
}
setUnreadCount(0);
};

useEffect(() => {
if (theme === 'dark') {
document.body.setAttribute('theme-mode', 'dark');
Expand Down Expand Up @@ -353,15 +409,14 @@ const HeaderBar = () => {
}
};

// 检查当前路由是否以/console开头
const isConsoleRoute = location.pathname.startsWith('/console');

return (
<header className="text-semi-color-text-0 sticky top-0 z-50 transition-colors duration-300 bg-white/75 dark:bg-zinc-900/75 backdrop-blur-lg">
<NoticeModal
visible={noticeVisible}
onClose={() => setNoticeVisible(false)}
onClose={handleNoticeClose}
isMobile={styleState.isMobile}
defaultTab={unreadCount > 0 ? 'system' : 'inApp'}
unreadKeys={getUnreadKeys()}
/>
<div className="w-full px-2">
<div className="flex items-center justify-between h-16">
Expand Down Expand Up @@ -462,14 +517,27 @@ const HeaderBar = () => {
</Dropdown>
)}

<Button
icon={<IconBell className="text-lg" />}
aria-label={t('系统公告')}
onClick={() => setNoticeVisible(true)}
theme="borderless"
type="tertiary"
className="!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2"
/>
{unreadCount > 0 ? (
<Badge count={unreadCount} type="danger" overflowCount={99}>
<Button
icon={<IconBell className="text-lg" />}
aria-label={t('系统公告')}
onClick={handleNoticeOpen}
theme="borderless"
type="tertiary"
className="!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2"
/>
</Badge>
) : (
<Button
icon={<IconBell className="text-lg" />}
aria-label={t('系统公告')}
onClick={handleNoticeOpen}
theme="borderless"
type="tertiary"
className="!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2"
/>
)}

<Button
icon={theme === 'dark' ? <IconSun size="large" className="text-yellow-500" /> : <IconMoon size="large" className="text-gray-300" />}
Expand Down
Loading