Compare commits

...

11 Commits

Author SHA1 Message Date
CalciumIon
ed2ec69545 feat: 暂时禁用透传功能 2024-11-19 15:06:54 +08:00
CalciumIon
a167dd9a23 feat: 暂时禁用透传功能 2024-11-19 15:04:19 +08:00
CalciumIon
334a2424e9 fix: oauth aff 2024-11-18 18:53:55 +08:00
CalciumIon
7db703374c fix: oauth aff 2024-11-18 18:52:14 +08:00
Calcium-Ion
6a42ccf00e Merge pull request #569 from utopeadia/main
Modify the default gemini API to v1beta
2024-11-17 16:27:38 +08:00
Calcium-Ion
7aa7114bb9 Merge branch 'main' into main 2024-11-17 16:27:29 +08:00
Calcium-Ion
c3e6b2408e Merge pull request #570 from leezhuuuuu/main
增加对于gemini-exp-1114模型的支持,映射到v1beta
2024-11-17 16:26:36 +08:00
leezhuuuuu
4601932902 feat: add support for gemini-exp-1114 model / 添加 gemini-exp-1114 模型支持
# feat: add support for gemini-exp-1114 model / 添加 gemini-exp-1114 模型支持

## Changes / 更改内容
- Add gemini-exp-1114 to ModelList in constant.go
- Add gemini-exp-1114 to GeminiModelMap with v1beta API version
- 在 constant.go 的 ModelList 中添加 gemini-exp-1114 模型
- 在 GeminiModelMap 中添加 gemini-exp-1114 的 v1beta API 版本映射

## Testing / 测试情况
- [x] Tested gemini-exp-1114 model API calls / 已测试 gemini-exp-1114 模型的 API 调用
- [x] Verified existing models still work / 验证现有模型仍然正常工作
- [x] Confirmed v1beta API version works correctly / 确认 v1beta API 版本正常工作

## Related Issues / 相关问题
- Fix 404 error when calling gemini-exp-1114 model / 修复调用 gemini-exp-1114 模型时的 404 错误

## Implementation Details / 实现细节
- Use configuration-based approach instead of code modification / 使用基于配置的方式而不是修改代码
- Maintain clean separation of concerns / 保持关注点分离
- Keep backward compatibility / 保持向后兼容性

## Notes / 注意事项
- This PR follows the principle of minimal invasion / 本 PR 遵循最小侵入原则
- Configuration changes only / 仅包含配置更改
2024-11-16 21:52:37 +08:00
leezhuuuuu
5d96f7b2cc 增加对于gemini-exp-1114模型的支持,映射到v1beta
feat(gemini): add support for gemini-exp-1114 model

- Add gemini-exp-1114 to ModelList in constant.go
- Update GetRequestURL in adaptor.go to use v1beta API version for gemini-exp-1114
- Keep backward compatibility for other models

This change enables the use of the experimental gemini-exp-1114 model by correctly routing its requests to the v1beta API endpoint while maintaining existing functionality for other models.
2024-11-16 21:29:35 +08:00
HowieWood
8eb32e9b3f Modify the default gemini API to v1beta 2024-11-16 12:21:50 +00:00
CalciumIon
320e6ec5a4 fix: aws claude 2024-11-14 15:12:34 +08:00
13 changed files with 234 additions and 61 deletions

View File

@@ -53,7 +53,7 @@
# Gemini 安全设置
# GEMINI_SAFETY_SETTING=BLOCK_NONE
# Gemini版本设置
# GEMINI_MODEL_MAP=gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta
# GEMINI_MODEL_MAP=gemini-1.0-pro:v1
# Cohere 安全设置
# COHERE_SAFETY_SETTING=NONE
# 是否统计图片token

View File

@@ -74,7 +74,7 @@
- `GET_MEDIA_TOKEN`是统计图片token默认为 `true`关闭后将不再在本地计算图片token可能会导致和上游计费不同此项覆盖 `GET_MEDIA_TOKEN_NOT_STREAM` 选项作用。
- `GET_MEDIA_TOKEN_NOT_STREAM`:是否在非流(`stream=false`情况下统计图片token默认为 `true`
- `UPDATE_TASK`是否更新异步任务Midjourney、Suno默认为 `true`,关闭后将不会更新任务进度。
- `GEMINI_MODEL_MAP`Gemini模型指定版本(v1/v1beta),使用“模型:版本”指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置
- `GEMINI_MODEL_MAP`Gemini模型指定版本(v1/v1beta),使用“模型:版本”指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置(v1beta)
- `COHERE_SAFETY_SETTING`Cohere模型[安全设置](https://docs.cohere.com/docs/safety-modes#overview),可选值为 `NONE`, `CONTEXTUAL``STRICT`,默认为 `NONE`
## 部署
### 部署要求

View File

@@ -20,16 +20,7 @@ var GetMediaTokenNotStream = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STR
var UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
var GeminiModelMap = map[string]string{
"gemini-1.5-pro-latest": "v1beta",
"gemini-1.5-pro-001": "v1beta",
"gemini-1.5-pro": "v1beta",
"gemini-1.5-pro-exp-0801": "v1beta",
"gemini-1.5-pro-exp-0827": "v1beta",
"gemini-1.5-flash-latest": "v1beta",
"gemini-1.5-flash-exp-0827": "v1beta",
"gemini-1.5-flash-001": "v1beta",
"gemini-1.5-flash": "v1beta",
"gemini-ultra": "v1beta",
"gemini-1.0-pro": "v1",
}
func InitEnv() {

View File

@@ -142,8 +142,13 @@ func GitHubOAuth(c *gin.Context) {
user.Email = githubUser.Email
user.Role = common.RoleCommonUser
user.Status = common.UserStatusEnabled
affCode := session.Get("aff")
inviterId := 0
if affCode != nil {
inviterId, _ = model.GetUserIdByAffCode(affCode.(string))
}
if err := user.Insert(0); err != nil {
if err := user.Insert(inviterId); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
@@ -227,6 +232,10 @@ func GitHubBind(c *gin.Context) {
func GenerateOAuthCode(c *gin.Context) {
session := sessions.Default(c)
state := common.GetRandomString(12)
affCode := c.Query("aff")
if affCode != "" {
session.Set("aff", affCode)
}
session.Set("oauth_state", state)
err := session.Save()
if err != nil {

View File

@@ -237,7 +237,13 @@ func LinuxdoOAuth(c *gin.Context) {
user.Role = common.RoleCommonUser
user.Status = common.UserStatusEnabled
if err := user.Insert(0); err != nil {
affCode := session.Get("aff")
inviterId := 0
if affCode != nil {
inviterId, _ = model.GetUserIdByAffCode(affCode.(string))
}
if err := user.Insert(inviterId); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),

View File

@@ -7,9 +7,9 @@ import (
type AwsClaudeRequest struct {
// AnthropicVersion should be "bedrock-2023-05-31"
AnthropicVersion string `json:"anthropic_version"`
System string `json:"system"`
System string `json:"system,omitempty"`
Messages []claude.ClaudeMessage `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
MaxTokens uint `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
@@ -17,3 +17,18 @@ type AwsClaudeRequest struct {
Tools []claude.Tool `json:"tools,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"`
}
func copyRequest(req *claude.ClaudeRequest) *AwsClaudeRequest {
return &AwsClaudeRequest{
AnthropicVersion: "bedrock-2023-05-31",
System: req.System,
Messages: req.Messages,
MaxTokens: req.MaxTokens,
Temperature: req.Temperature,
TopP: req.TopP,
TopK: req.TopK,
StopSequences: req.StopSequences,
Tools: req.Tools,
ToolChoice: req.ToolChoice,
}
}

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"io"
"net/http"
@@ -78,13 +77,7 @@ func awsHandler(c *gin.Context, info *relaycommon.RelayInfo, requestMode int) (*
return wrapErr(errors.New("request not found")), nil
}
claudeReq := claudeReq_.(*claude.ClaudeRequest)
awsClaudeReq := &AwsClaudeRequest{
AnthropicVersion: "bedrock-2023-05-31",
}
if err = copier.Copy(awsClaudeReq, claudeReq); err != nil {
return wrapErr(errors.Wrap(err, "copy request")), nil
}
awsClaudeReq := copyRequest(claudeReq)
awsReq.Body, err = json.Marshal(awsClaudeReq)
if err != nil {
return wrapErr(errors.Wrap(err, "marshal request")), nil
@@ -136,12 +129,7 @@ func awsStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
}
claudeReq := claudeReq_.(*claude.ClaudeRequest)
awsClaudeReq := &AwsClaudeRequest{
AnthropicVersion: "bedrock-2023-05-31",
}
if err = copier.Copy(awsClaudeReq, claudeReq); err != nil {
return wrapErr(errors.Wrap(err, "copy request")), nil
}
awsClaudeReq := copyRequest(claudeReq)
awsReq.Body, err = json.Marshal(awsClaudeReq)
if err != nil {
return wrapErr(errors.Wrap(err, "marshal request")), nil

View File

@@ -30,13 +30,13 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
// 从映射中获取模型名称对应的版本,如果找不到就使用 info.ApiVersion 或默认的版本 "v1"
// 从映射中获取模型名称对应的版本,如果找不到就使用 info.ApiVersion 或默认的版本 "v1beta"
version, beta := constant.GeminiModelMap[info.UpstreamModelName]
if !beta {
if info.ApiVersion != "" {
version = info.ApiVersion
} else {
version = "v1"
version = "v1beta"
}
}

View File

@@ -7,6 +7,7 @@ const (
var ModelList = []string{
"gemini-1.0-pro-latest", "gemini-1.0-pro-001", "gemini-1.5-pro-latest", "gemini-1.5-flash-latest", "gemini-ultra",
"gemini-1.0-pro-vision-latest", "gemini-1.0-pro-vision-001", "gemini-1.5-pro-exp-0827", "gemini-1.5-flash-exp-0827",
"gemini-exp-1114",
}
var ChannelName = "google gemini"

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/bytedance/sonic"
"io"
"math"
"net/http"
@@ -76,7 +77,7 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
}
// map model name
isModelMapped := false
//isModelMapped := false
modelMapping := c.GetString("model_mapping")
//isModelMapped := false
if modelMapping != "" && modelMapping != "{}" {
@@ -86,7 +87,7 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
return service.OpenAIErrorWrapperLocal(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError)
}
if modelMap[textRequest.Model] != "" {
isModelMapped = true
//isModelMapped = true
textRequest.Model = modelMap[textRequest.Model]
// set upstream model name
//isModelMapped = true
@@ -165,23 +166,25 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
adaptor.Init(relayInfo)
var requestBody io.Reader
if relayInfo.ChannelType == common.ChannelTypeOpenAI && !isModelMapped {
body, err := common.GetRequestBody(c)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "get_request_body_failed", http.StatusInternalServerError)
}
requestBody = bytes.NewBuffer(body)
} else {
convertedRequest, err := adaptor.ConvertRequest(c, relayInfo, textRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "convert_request_failed", http.StatusInternalServerError)
}
jsonData, err := json.Marshal(convertedRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "json_marshal_failed", http.StatusInternalServerError)
}
requestBody = bytes.NewBuffer(jsonData)
//if relayInfo.ChannelType == common.ChannelTypeOpenAI && !isModelMapped {
// body, err := common.GetRequestBody(c)
// if err != nil {
// return service.OpenAIErrorWrapperLocal(err, "get_request_body_failed", http.StatusInternalServerError)
// }
// requestBody = bytes.NewBuffer(body)
//} else {
//
//}
convertedRequest, err := adaptor.ConvertRequest(c, relayInfo, textRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "convert_request_failed", http.StatusInternalServerError)
}
jsonData, err := sonic.Marshal(convertedRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "json_marshal_failed", http.StatusInternalServerError)
}
requestBody = bytes.NewBuffer(jsonData)
statusCodeMappingStr := c.GetString("status_code_mapping")
var httpResp *http.Response

View File

@@ -44,8 +44,15 @@ const LoginForm = () => {
const [turnstileToken, setTurnstileToken] = useState('');
let navigate = useNavigate();
const [status, setStatus] = useState({});
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
const logo = getLogo();
let affCode = new URLSearchParams(window.location.search).get('aff');
if (affCode) {
localStorage.setItem('aff', affCode);
}
useEffect(() => {
if (searchParams.get('expired')) {
showError('未登录或登录已过期,请重新登录!');
@@ -61,7 +68,6 @@ const LoginForm = () => {
}
}, []);
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
const onWeChatLoginClicked = () => {
setShowWeChatLoginModal(true);

View File

@@ -1,10 +1,16 @@
import React, { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { API, getLogo, showError, showInfo, showSuccess } from '../helpers';
import { API, getLogo, showError, showInfo, showSuccess, updateAPI } from '../helpers';
import Turnstile from 'react-turnstile';
import { Button, Card, Form, Layout } from '@douyinfe/semi-ui';
import { Button, Card, Divider, Form, Icon, Layout, Modal } from '@douyinfe/semi-ui';
import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
import { IconGithubLogo } from '@douyinfe/semi-icons';
import { onGitHubOAuthClicked, onLinuxDOOAuthClicked } from './utils.js';
import LinuxDoIcon from './LinuxDoIcon.js';
import WeChatIcon from './WeChatIcon.js';
import TelegramLoginButton from 'react-telegram-login/src';
import { setUserData } from '../helpers/data.js';
const RegisterForm = () => {
const [inputs, setInputs] = useState({
@@ -20,7 +26,11 @@ const RegisterForm = () => {
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
const [turnstileToken, setTurnstileToken] = useState('');
const [loading, setLoading] = useState(false);
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
const [status, setStatus] = useState({});
let navigate = useNavigate();
const logo = getLogo();
let affCode = new URLSearchParams(window.location.search).get('aff');
if (affCode) {
localStorage.setItem('aff', affCode);
@@ -30,6 +40,7 @@ const RegisterForm = () => {
let status = localStorage.getItem('status');
if (status) {
status = JSON.parse(status);
setStatus(status);
setShowEmailVerification(status.email_verification);
if (status.turnstile_check) {
setTurnstileEnabled(true);
@@ -38,7 +49,32 @@ const RegisterForm = () => {
}
});
let navigate = useNavigate();
const onWeChatLoginClicked = () => {
setShowWeChatLoginModal(true);
};
const onSubmitWeChatVerificationCode = async () => {
if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试Turnstile 正在检查用户环境!');
return;
}
const res = await API.get(
`/api/oauth/wechat?code=${inputs.wechat_verification_code}`,
);
const { success, message, data } = res.data;
if (success) {
userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data));
setUserData(data);
updateAPI();
navigate('/');
showSuccess('登录成功!');
setShowWeChatLoginModal(false);
} else {
showError(message);
}
};
function handleChange(name, value) {
setInputs((inputs) => ({ ...inputs, [name]: value }));
@@ -189,14 +225,127 @@ const RegisterForm = () => {
</Link>
</Text>
</div>
{status.github_oauth ||
status.wechat_login ||
status.telegram_oauth ||
status.linuxdo_oauth ? (
<>
<Divider margin='12px' align='center'>
第三方登录
</Divider>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: 20,
}}
>
{status.github_oauth ? (
<Button
type='primary'
icon={<IconGithubLogo />}
onClick={() =>
onGitHubOAuthClicked(status.github_client_id)
}
/>
) : (
<></>
)}
{status.linuxdo_oauth ? (
<Button
icon={<LinuxDoIcon />}
onClick={() =>
onLinuxDOOAuthClicked(status.linuxdo_client_id)
}
/>
) : (
<></>
)}
{status.wechat_login ? (
<Button
type='primary'
style={{ color: 'rgba(var(--semi-green-5), 1)' }}
icon={<Icon svg={<WeChatIcon />} />}
onClick={onWeChatLoginClicked}
/>
) : (
<></>
)}
</div>
{status.telegram_oauth ? (
<>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: 5,
}}
>
<TelegramLoginButton
dataOnauth={onTelegramLoginClicked}
botName={status.telegram_bot_name}
/>
</div>
</>
) : (
<></>
)}
</>
) : (
<></>
)}
</Card>
{turnstileEnabled ? (
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
<Modal
title='微信扫码登录'
visible={showWeChatLoginModal}
maskClosable={true}
onOk={onSubmitWeChatVerificationCode}
onCancel={() => setShowWeChatLoginModal(false)}
okText={'登录'}
size={'small'}
centered={true}
>
<div
style={{
display: 'flex',
alignItem: 'center',
flexDirection: 'column',
}}
/>
>
<img src={status.wechat_qrcode} />
</div>
<div style={{ textAlign: 'center' }}>
<p>
微信扫码关注公众号输入验证码获取验证码三分钟内有效
</p>
</div>
<Form size='large'>
<Form.Input
field={'wechat_verification_code'}
placeholder='验证码'
label={'验证码'}
value={inputs.wechat_verification_code}
onChange={(value) =>
handleChange('wechat_verification_code', value)
}
/>
</Form>
</Modal>
{turnstileEnabled ? (
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: 20,
}}
>
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
}}
/>
</div>
) : (
<></>
)}

View File

@@ -1,7 +1,12 @@
import { API, showError } from '../helpers';
export async function getOAuthState() {
const res = await API.get('/api/oauth/state');
let path = '/api/oauth/state';
let affCode = localStorage.getItem('aff');
if (affCode && affCode.length > 0) {
path += `?aff=${affCode}`;
}
const res = await API.get(path);
const { success, message, data } = res.data;
if (success) {
return data;