Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 1 deletion controller/topup_stripe.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ func genStripeLink(referenceId string, customerId string, email string, amount i
Quantity: stripe.Int64(amount),
},
},
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
AllowPromotionCodes: stripe.Bool(setting.StripePromotionCodesEnabled),
}

if "" == customerId {
Expand Down
1 change: 1 addition & 0 deletions dto/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ type GeminiChatTool struct {
GoogleSearchRetrieval any `json:"googleSearchRetrieval,omitempty"`
CodeExecution any `json:"codeExecution,omitempty"`
FunctionDeclarations any `json:"functionDeclarations,omitempty"`
URLContext any `json:"urlContext,omitempty"`
}

type GeminiChatGenerationConfig struct {
Expand Down
18 changes: 18 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"embed"
"fmt"
"log"
Expand All @@ -16,6 +17,7 @@ import (
"one-api/setting/ratio_setting"
"os"
"strconv"
"strings"

"github.com/bytedance/gopkg/util/gopool"
"github.com/gin-contrib/sessions"
Expand Down Expand Up @@ -145,6 +147,22 @@ func main() {
})
server.Use(sessions.Sessions("session", store))

analyticsInjectBuilder := &strings.Builder{}
if os.Getenv("UMAMI_WEBSITE_ID") != "" {
umamiSiteID := os.Getenv("UMAMI_WEBSITE_ID")
umamiScriptURL := os.Getenv("UMAMI_SCRIPT_URL")
if umamiScriptURL == "" {
umamiScriptURL = "https://analytics.umami.is/script.js"
}
analyticsInjectBuilder.WriteString("<script defer src=\"")
analyticsInjectBuilder.WriteString(umamiScriptURL)
analyticsInjectBuilder.WriteString("\" data-website-id=\"")
analyticsInjectBuilder.WriteString(umamiSiteID)
analyticsInjectBuilder.WriteString("\"></script>")
}
analyticsInject := analyticsInjectBuilder.String()
indexPage = bytes.ReplaceAll(indexPage, []byte("<analytics></analytics>\n"), []byte(analyticsInject))

router.SetRouter(server, buildFS, indexPage)
var port = os.Getenv("PORT")
if port == "" {
Expand Down
3 changes: 3 additions & 0 deletions model/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func InitOptionMap() {
common.OptionMap["StripeWebhookSecret"] = setting.StripeWebhookSecret
common.OptionMap["StripePriceId"] = setting.StripePriceId
common.OptionMap["StripeUnitPrice"] = strconv.FormatFloat(setting.StripeUnitPrice, 'f', -1, 64)
common.OptionMap["StripePromotionCodesEnabled"] = strconv.FormatBool(setting.StripePromotionCodesEnabled)
common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
common.OptionMap["Chats"] = setting.Chats2JsonString()
common.OptionMap["AutoGroups"] = setting.AutoGroups2JsonString()
Expand Down Expand Up @@ -330,6 +331,8 @@ func updateOptionMap(key string, value string) (err error) {
setting.StripeUnitPrice, _ = strconv.ParseFloat(value, 64)
case "StripeMinTopUp":
setting.StripeMinTopUp, _ = strconv.Atoi(value)
case "StripePromotionCodesEnabled":
setting.StripePromotionCodesEnabled = value == "true"
case "TopupGroupRatio":
err = common.UpdateTopupGroupRatioByJSONString(value)
case "GitHubClientId":
Expand Down
10 changes: 10 additions & 0 deletions relay/channel/gemini/relay-gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
functions := make([]dto.FunctionRequest, 0, len(textRequest.Tools))
googleSearch := false
codeExecution := false
urlContext := false
for _, tool := range textRequest.Tools {
if tool.Function.Name == "googleSearch" {
googleSearch = true
Expand All @@ -254,6 +255,10 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
codeExecution = true
continue
}
if tool.Function.Name == "urlContext" {
urlContext = true
continue
}
if tool.Function.Parameters != nil {

params, ok := tool.Function.Parameters.(map[string]interface{})
Expand Down Expand Up @@ -281,6 +286,11 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
GoogleSearch: make(map[string]string),
})
}
if urlContext {
geminiTools = append(geminiTools, dto.GeminiChatTool{
URLContext: make(map[string]string),
})
}
if len(functions) > 0 {
geminiTools = append(geminiTools, dto.GeminiChatTool{
FunctionDeclarations: functions,
Expand Down
1 change: 1 addition & 0 deletions setting/payment_stripe.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ var StripeWebhookSecret = ""
var StripePriceId = ""
var StripeUnitPrice = 8.0
var StripeMinTopUp = 1
var StripePromotionCodesEnabled = false
1 change: 1 addition & 0 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
content="OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用"
/>
<title>New API</title>
<analytics></analytics>
</head>

<body>
Expand Down
1 change: 1 addition & 0 deletions web/src/components/settings/PaymentSetting.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const PaymentSetting = () => {
StripePriceId: '',
StripeUnitPrice: 8.0,
StripeMinTopUp: 1,
StripePromotionCodesEnabled: false,
});

let [loading, setLoading] = useState(false);
Expand Down
11 changes: 6 additions & 5 deletions web/src/components/settings/personal/cards/AccountManagement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,10 @@ const AccountManagement = ({
{t('微信')}
</div>
<div className='text-sm text-gray-500 truncate'>
{userState.user && userState.user.wechat_id !== ''
? t('已绑定')
: t('未绑定')}
{renderAccountInfo(
userState.user?.wechat_id,
t('微信 ID'),
)}
</div>
</div>
</div>
Expand All @@ -179,7 +180,7 @@ const AccountManagement = ({
disabled={!status.wechat_login}
onClick={() => setShowWeChatBindModal(true)}
>
{userState.user && userState.user.wechat_id !== ''
{userState.user && userState.user?.wechat_id
? t('修改绑定')
: status.wechat_login
? t('绑定')
Expand Down Expand Up @@ -298,7 +299,7 @@ const AccountManagement = ({
</div>
<div className='flex-shrink-0'>
{status.telegram_oauth ? (
userState.user.telegram_id !== '' ? (
userState.user?.telegram_id ? (
<Button disabled={true} size='small'>
{t('已绑定')}
</Button>
Expand Down
120 changes: 97 additions & 23 deletions web/src/components/table/channels/modals/EditChannelModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,9 @@ const EditChannelModal = (props) => {
delete localInputs.key;
}
} else {
localInputs.key = batch ? JSON.stringify(keys) : JSON.stringify(keys[0]);
localInputs.key = batch
? JSON.stringify(keys)
: JSON.stringify(keys[0]);
}
}
}
Expand Down Expand Up @@ -919,6 +921,56 @@ const EditChannelModal = (props) => {
}
};

// 密钥去重函数
const deduplicateKeys = () => {
const currentKey = formApiRef.current?.getValue('key') || inputs.key || '';

if (!currentKey.trim()) {
showInfo(t('请先输入密钥'));
return;
}

// 按行分割密钥
const keyLines = currentKey.split('\n');
const beforeCount = keyLines.length;

// 使用哈希表去重,保持原有顺序
const keySet = new Set();
const deduplicatedKeys = [];

keyLines.forEach((line) => {
const trimmedLine = line.trim();
if (trimmedLine && !keySet.has(trimmedLine)) {
keySet.add(trimmedLine);
deduplicatedKeys.push(trimmedLine);
}
});

const afterCount = deduplicatedKeys.length;
const deduplicatedKeyText = deduplicatedKeys.join('\n');

// 更新表单和状态
if (formApiRef.current) {
formApiRef.current.setValue('key', deduplicatedKeyText);
}
handleInputChange('key', deduplicatedKeyText);

// 显示去重结果
const message = t(
'去重完成:去重前 {{before}} 个密钥,去重后 {{after}} 个密钥',
{
before: beforeCount,
after: afterCount,
},
);

if (beforeCount === afterCount) {
showInfo(t('未发现重复密钥'));
} else {
showSuccess(message);
}
};

const addCustomModels = () => {
if (customModel.trim() === '') return;
const modelArray = customModel.split(',').map((model) => model.trim());
Expand Down Expand Up @@ -1014,24 +1066,41 @@ const EditChannelModal = (props) => {
</Checkbox>
)}
{batch && (
<Checkbox
disabled={isEdit}
checked={multiToSingle}
onChange={() => {
setMultiToSingle((prev) => !prev);
setInputs((prev) => {
const newInputs = { ...prev };
if (!multiToSingle) {
newInputs.multi_key_mode = multiKeyMode;
} else {
delete newInputs.multi_key_mode;
}
return newInputs;
});
}}
>
{t('密钥聚合模式')}
</Checkbox>
<>
<Checkbox
disabled={isEdit}
checked={multiToSingle}
onChange={() => {
setMultiToSingle((prev) => {
const nextValue = !prev;
setInputs((prevInputs) => {
const newInputs = { ...prevInputs };
if (nextValue) {
newInputs.multi_key_mode = multiKeyMode;
} else {
delete newInputs.multi_key_mode;
}
return newInputs;
});
return nextValue;
});
}}
>
{t('密钥聚合模式')}
</Checkbox>

{inputs.type !== 41 && (
<Button
size='small'
type='tertiary'
theme='outline'
onClick={deduplicateKeys}
style={{ textDecoration: 'underline' }}
>
{t('密钥去重')}
</Button>
)}
</>
)}
</Space>
) : null;
Expand Down Expand Up @@ -1218,7 +1287,10 @@ const EditChannelModal = (props) => {
value={inputs.vertex_key_type || 'json'}
onChange={(value) => {
// 更新设置中的 vertex_key_type
handleChannelOtherSettingsChange('vertex_key_type', value);
handleChannelOtherSettingsChange(
'vertex_key_type',
value,
);
// 切换为 api_key 时,关闭批量与手动/文件切换,并清理已选文件
if (value === 'api_key') {
setBatch(false);
Expand All @@ -1238,7 +1310,8 @@ const EditChannelModal = (props) => {
/>
)}
{batch ? (
inputs.type === 41 && (inputs.vertex_key_type || 'json') === 'json' ? (
inputs.type === 41 &&
(inputs.vertex_key_type || 'json') === 'json' ? (
<Form.Upload
field='vertex_files'
label={t('密钥文件 (.json)')}
Expand Down Expand Up @@ -1274,7 +1347,7 @@ const EditChannelModal = (props) => {
autoComplete='new-password'
onChange={(value) => handleInputChange('key', value)}
extraText={
<div className='flex items-center gap-2'>
<div className='flex items-center gap-2 flex-wrap'>
{isEdit &&
isMultiKeyChannel &&
keyMode === 'append' && (
Expand Down Expand Up @@ -1302,7 +1375,8 @@ const EditChannelModal = (props) => {
)
) : (
<>
{inputs.type === 41 && (inputs.vertex_key_type || 'json') === 'json' ? (
{inputs.type === 41 &&
(inputs.vertex_key_type || 'json') === 'json' ? (
<>
{!batch && (
<div className='flex items-center justify-between mb-3'>
Expand Down
Loading