diff --git a/relay/channel/ali/adaptor.go b/relay/channel/ali/adaptor.go
index 31e926d6..f30d4dc4 100644
--- a/relay/channel/ali/adaptor.go
+++ b/relay/channel/ali/adaptor.go
@@ -31,6 +31,8 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
switch info.RelayMode {
case constant.RelayModeEmbeddings:
fullRequestURL = fmt.Sprintf("%s/api/v1/services/embeddings/text-embedding/text-embedding", info.BaseUrl)
+ case constant.RelayModeRerank:
+ fullRequestURL = fmt.Sprintf("%s/api/v1/services/rerank/text-rerank/text-rerank", info.BaseUrl)
case constant.RelayModeImagesGenerations:
fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text2image/image-synthesis", info.BaseUrl)
case constant.RelayModeCompletions:
@@ -76,7 +78,7 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
}
func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
- return nil, errors.New("not implemented")
+ return ConvertRerankRequest(request), nil
}
func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
@@ -103,6 +105,8 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
err, usage = aliImageHandler(c, resp, info)
case constant.RelayModeEmbeddings:
err, usage = aliEmbeddingHandler(c, resp)
+ case constant.RelayModeRerank:
+ err, usage = RerankHandler(c, resp, info)
default:
if info.IsStream {
err, usage = openai.OaiStreamHandler(c, resp, info)
diff --git a/relay/channel/ali/constants.go b/relay/channel/ali/constants.go
index 46de5e40..df64439b 100644
--- a/relay/channel/ali/constants.go
+++ b/relay/channel/ali/constants.go
@@ -8,6 +8,7 @@ var ModelList = []string{
"qwq-32b",
"qwen3-235b-a22b",
"text-embedding-v1",
+ "gte-rerank-v2",
}
var ChannelName = "ali"
diff --git a/relay/channel/ali/dto.go b/relay/channel/ali/dto.go
index f51286ad..dbd18968 100644
--- a/relay/channel/ali/dto.go
+++ b/relay/channel/ali/dto.go
@@ -1,5 +1,7 @@
package ali
+import "one-api/dto"
+
type AliMessage struct {
Content string `json:"content"`
Role string `json:"role"`
@@ -97,3 +99,28 @@ type AliImageRequest struct {
} `json:"parameters,omitempty"`
ResponseFormat string `json:"response_format,omitempty"`
}
+
+type AliRerankParameters struct {
+ TopN *int `json:"top_n,omitempty"`
+ ReturnDocuments *bool `json:"return_documents,omitempty"`
+}
+
+type AliRerankInput struct {
+ Query string `json:"query"`
+ Documents []any `json:"documents"`
+}
+
+type AliRerankRequest struct {
+ Model string `json:"model"`
+ Input AliRerankInput `json:"input"`
+ Parameters AliRerankParameters `json:"parameters,omitempty"`
+}
+
+type AliRerankResponse struct {
+ Output struct {
+ Results []dto.RerankResponseResult `json:"results"`
+ } `json:"output"`
+ Usage AliUsage `json:"usage"`
+ RequestId string `json:"request_id"`
+ AliError
+}
diff --git a/relay/channel/ali/rerank.go b/relay/channel/ali/rerank.go
new file mode 100644
index 00000000..c9ae066a
--- /dev/null
+++ b/relay/channel/ali/rerank.go
@@ -0,0 +1,83 @@
+package ali
+
+import (
+ "encoding/json"
+ "io"
+ "net/http"
+ "one-api/dto"
+ relaycommon "one-api/relay/common"
+ "one-api/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+func ConvertRerankRequest(request dto.RerankRequest) *AliRerankRequest {
+ returnDocuments := request.ReturnDocuments
+ if returnDocuments == nil {
+ t := true
+ returnDocuments = &t
+ }
+ return &AliRerankRequest{
+ Model: request.Model,
+ Input: AliRerankInput{
+ Query: request.Query,
+ Documents: request.Documents,
+ },
+ Parameters: AliRerankParameters{
+ TopN: &request.TopN,
+ ReturnDocuments: returnDocuments,
+ },
+ }
+}
+
+func RerankHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
+ responseBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
+ }
+ err = resp.Body.Close()
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
+ }
+
+ var aliResponse AliRerankResponse
+ err = json.Unmarshal(responseBody, &aliResponse)
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
+ }
+
+ if aliResponse.Code != "" {
+ return &dto.OpenAIErrorWithStatusCode{
+ Error: dto.OpenAIError{
+ Message: aliResponse.Message,
+ Type: aliResponse.Code,
+ Param: aliResponse.RequestId,
+ Code: aliResponse.Code,
+ },
+ StatusCode: resp.StatusCode,
+ }, nil
+ }
+
+ usage := dto.Usage{
+ PromptTokens: aliResponse.Usage.TotalTokens,
+ CompletionTokens: 0,
+ TotalTokens: aliResponse.Usage.TotalTokens,
+ }
+ rerankResponse := dto.RerankResponse{
+ Results: aliResponse.Output.Results,
+ Usage: usage,
+ }
+
+ jsonResponse, err := json.Marshal(rerankResponse)
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
+ }
+ c.Writer.Header().Set("Content-Type", "application/json")
+ c.Writer.WriteHeader(resp.StatusCode)
+ _, err = c.Writer.Write(jsonResponse)
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "write_response_body_failed", http.StatusInternalServerError), nil
+ }
+
+ return nil, &usage
+}
diff --git a/web/src/components/auth/OAuth2Callback.js b/web/src/components/auth/OAuth2Callback.js
index 83f9db46..6d0bbe70 100644
--- a/web/src/components/auth/OAuth2Callback.js
+++ b/web/src/components/auth/OAuth2Callback.js
@@ -1,15 +1,16 @@
import React, { useContext, useEffect, useState } from 'react';
-import { Spin, Typography, Space } from '@douyinfe/semi-ui';
import { useNavigate, useSearchParams } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
import { API, showError, showSuccess, updateAPI, setUserData } from '../../helpers';
import { UserContext } from '../../context/User';
+import Loading from '../common/Loading';
const OAuth2Callback = (props) => {
+ const { t } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();
const [userState, userDispatch] = useContext(UserContext);
- const [prompt, setPrompt] = useState('处理中...');
- const [processing, setProcessing] = useState(true);
+ const [prompt, setPrompt] = useState(t('处理中...'));
let navigate = useNavigate();
@@ -20,25 +21,25 @@ const OAuth2Callback = (props) => {
const { success, message, data } = res.data;
if (success) {
if (message === 'bind') {
- showSuccess('绑定成功!');
- navigate('/setting');
+ showSuccess(t('绑定成功!'));
+ navigate('/console/setting');
} else {
userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data));
setUserData(data);
updateAPI();
- showSuccess('登录成功!');
- navigate('/token');
+ showSuccess(t('登录成功!'));
+ navigate('/console/token');
}
} else {
showError(message);
if (count === 0) {
- setPrompt(`操作失败,重定向至登录界面中...`);
- navigate('/setting'); // in case this is failed to bind GitHub
+ setPrompt(t('操作失败,重定向至登录界面中...'));
+ navigate('/console/setting'); // in case this is failed to bind GitHub
return;
}
count++;
- setPrompt(`出现错误,第 ${count} 次重试中...`);
+ setPrompt(t('出现错误,第 ${count} 次重试中...', { count }));
await new Promise((resolve) => setTimeout(resolve, count * 2000));
await sendCode(code, state, count);
}
@@ -50,17 +51,7 @@ const OAuth2Callback = (props) => {
sendCode(code, state, 0).then();
}, []);
- return (
-
- );
+ return ;
};
export default OAuth2Callback;
diff --git a/web/src/components/auth/PasswordResetConfirm.js b/web/src/components/auth/PasswordResetConfirm.js
index dc69ccdc..025161ac 100644
--- a/web/src/components/auth/PasswordResetConfirm.js
+++ b/web/src/components/auth/PasswordResetConfirm.js
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react';
import { API, copy, showError, showNotice, getLogo, getSystemName } from '../../helpers';
import { useSearchParams, Link } from 'react-router-dom';
-import { Button, Card, Form, Typography } from '@douyinfe/semi-ui';
-import { IconMail, IconLock } from '@douyinfe/semi-icons';
+import { Button, Card, Form, Typography, Banner } from '@douyinfe/semi-ui';
+import { IconMail, IconLock, IconCopy } from '@douyinfe/semi-icons';
import { useTranslation } from 'react-i18next';
import Background from '/example.png';
@@ -15,13 +15,14 @@ const PasswordResetConfirm = () => {
token: '',
});
const { email, token } = inputs;
+ const isValidResetLink = email && token;
const [loading, setLoading] = useState(false);
const [disableButton, setDisableButton] = useState(false);
const [countdown, setCountdown] = useState(30);
const [newPassword, setNewPassword] = useState('');
-
const [searchParams, setSearchParams] = useSearchParams();
+ const [formApi, setFormApi] = useState(null);
const logo = getLogo();
const systemName = getSystemName();
@@ -30,10 +31,16 @@ const PasswordResetConfirm = () => {
let token = searchParams.get('token');
let email = searchParams.get('email');
setInputs({
- token,
- email,
+ token: token || '',
+ email: email || '',
});
- }, []);
+ if (formApi) {
+ formApi.setValues({
+ email: email || '',
+ newPassword: newPassword || ''
+ });
+ }
+ }, [searchParams, newPassword, formApi]);
useEffect(() => {
let countdownInterval = null;
@@ -49,7 +56,10 @@ const PasswordResetConfirm = () => {
}, [disableButton, countdown]);
async function handleSubmit(e) {
- if (!email || !token) return;
+ if (!email || !token) {
+ showError(t('无效的重置链接,请重新发起密码重置请求'));
+ return;
+ }
setDisableButton(true);
setLoading(true);
const res = await API.post(`/api/user/reset`, {
@@ -61,7 +71,7 @@ const PasswordResetConfirm = () => {
let password = res.data.data;
setNewPassword(password);
await copy(password);
- showNotice(`${t('密码已重置并已复制到剪贴板')}: ${password}`);
+ showNotice(`${t('密码已重置并已复制到剪贴板:')} ${password}`);
} else {
showError(message);
}
@@ -94,16 +104,28 @@ const PasswordResetConfirm = () => {
{t('密码重置确认')}
-
}
+ placeholder={email ? '' : t('等待获取邮箱信息...')}
/>
{newPassword && (
@@ -113,14 +135,21 @@ const PasswordResetConfirm = () => {
name="newPassword"
size="large"
className="!rounded-md"
- value={newPassword}
- readOnly
+ disabled={true}
prefix={}
- onClick={(e) => {
- e.target.select();
- navigator.clipboard.writeText(newPassword);
- showNotice(`${t('密码已复制到剪贴板')}: ${newPassword}`);
- }}
+ suffix={
+ }
+ type="tertiary"
+ theme="borderless"
+ onClick={async () => {
+ await copy(newPassword);
+ showNotice(`${t('密码已复制到剪贴板:')} ${newPassword}`);
+ }}
+ >
+ {t('复制')}
+
+ }
/>
)}
@@ -133,9 +162,9 @@ const PasswordResetConfirm = () => {
size="large"
onClick={handleSubmit}
loading={loading}
- disabled={disableButton || newPassword}
+ disabled={disableButton || newPassword || !isValidResetLink}
>
- {newPassword ? t('密码重置完成') : t('提交')}
+ {newPassword ? t('密码重置完成') : t('确认重置密码')}
diff --git a/web/src/components/auth/PasswordResetForm.js b/web/src/components/auth/PasswordResetForm.js
index 6abdfaa5..4ff7882f 100644
--- a/web/src/components/auth/PasswordResetForm.js
+++ b/web/src/components/auth/PasswordResetForm.js
@@ -55,7 +55,10 @@ const PasswordResetForm = () => {
}
async function handleSubmit(e) {
- if (!email) return;
+ if (!email) {
+ showError(t('请输入邮箱地址'));
+ return;
+ }
if (turnstileEnabled && turnstileToken === '') {
showInfo(t('请稍后几秒重试,Turnstile 正在检查用户环境!'));
return;
diff --git a/web/src/components/common/Loading.js b/web/src/components/common/Loading.js
index 980e9cb1..a12be053 100644
--- a/web/src/components/common/Loading.js
+++ b/web/src/components/common/Loading.js
@@ -14,7 +14,7 @@ const Loading = ({ prompt: name = '', size = 'large' }) => {
tip={null}
/>
- {name ? t('加载{{name}}中...', { name }) : t('加载中...')}
+ {name ? t('{{name}}', { name }) : t('加载中...')}
diff --git a/web/src/components/layout/Footer.js b/web/src/components/layout/Footer.js
index d53ae2ed..4f44c1dc 100644
--- a/web/src/components/layout/Footer.js
+++ b/web/src/components/layout/Footer.js
@@ -40,36 +40,36 @@ const FooterBar = () => {
@@ -81,14 +81,12 @@ const FooterBar = () => {
© {currentYear} {systemName}. {t('版权所有')}
- {isDemoSiteMode && (
-
- )}
+
), [logo, systemName, t, currentYear, isDemoSiteMode]);
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json
index 29469d14..520e30c2 100644
--- a/web/src/i18n/locales/en.json
+++ b/web/src/i18n/locales/en.json
@@ -265,10 +265,15 @@
"设置页脚": "Set Footer",
"新版本": "New Version",
"关闭": "Close",
- "密码已重置并已复制到剪贴板": "Password has been reset and copied to clipboard",
+ "密码已重置并已复制到剪贴板:": "Password has been reset and copied to clipboard: ",
+ "密码已复制到剪贴板:": "Password has been copied to clipboard: ",
"密码重置确认": "Password Reset Confirmation",
"邮箱地址": "Email address",
"提交": "Submit",
+ "等待获取邮箱信息...": "Waiting to get email information...",
+ "确认重置密码": "Confirm Password Reset",
+ "无效的重置链接,请重新发起密码重置请求": "Invalid reset link, please initiate a new password reset request",
+ "请输入邮箱地址": "Please enter the email address",
"请稍后几秒重试": "Please retry in a few seconds",
"正在检查用户环境": "Checking user environment",
"重置邮件发送成功": "Reset mail sent successfully",
@@ -1404,8 +1409,13 @@
"演示站点": "Demo Site",
"页面未找到,请检查您的浏览器地址是否正确": "Page not found, please check if your browser address is correct",
"New API项目仓库地址:": "New API project repository address: ",
- "NewAPI © {{currentYear}} QuantumNous | 基于 One API v0.5.4 © 2023 JustSong。": "NewAPI © {{currentYear}} QuantumNous | Based on One API v0.5.4 © 2023 JustSong.",
- "本项目根据MIT许可证授权,需在遵守Apache-2.0协议的前提下使用。": "This project is licensed under the MIT License and must be used in compliance with the Apache-2.0 License.",
+ "© {{currentYear}}": "© {{currentYear}}",
+ "| 基于": " | Based on ",
+ "MIT许可证": "MIT License",
+ "Apache-2.0协议": "Apache-2.0 License",
+ "本项目根据": "This project is licensed under the ",
+ "授权,需在遵守": " and must be used in compliance with the ",
+ "的前提下使用。": ".",
"管理员暂时未设置任何关于内容": "The administrator has not set any custom About content yet",
"早上好": "Good morning",
"中午好": "Good afternoon",
@@ -1531,6 +1541,7 @@
"关闭公告": "Close Notice",
"搜索条件": "Search Conditions",
"加载中...": "Loading...",
+ "正在跳转...": "Redirecting...",
"暂无公告": "No Notice",
"操练场": "Playground",
"欢迎使用,请完成以下设置以开始使用系统": "Welcome to use, please complete the following settings to start using the system",
diff --git a/web/src/pages/About/index.js b/web/src/pages/About/index.js
index 42ac09e0..3259449e 100644
--- a/web/src/pages/About/index.js
+++ b/web/src/pages/About/index.js
@@ -3,7 +3,6 @@ import { API, showError } from '../../helpers';
import { marked } from 'marked';
import { Empty } from '@douyinfe/semi-ui';
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
-import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const About = () => {
@@ -42,14 +41,65 @@ const About = () => {
);
diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js
index feed8beb..04dc284b 100644
--- a/web/src/pages/Channel/EditChannel.js
+++ b/web/src/pages/Channel/EditChannel.js
@@ -846,7 +846,7 @@ const EditChannel = (props) => {
className="!rounded-lg font-mono"
/>
handleInputChange('model_mapping', JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2))}
>
{t('填入模板')}
@@ -940,7 +940,7 @@ const EditChannel = (props) => {
className="!rounded-lg font-mono"
/>
handleInputChange('other', JSON.stringify(REGION_EXAMPLE, null, 2))}
>
{t('填入模板')}
@@ -1062,7 +1062,7 @@ const EditChannel = (props) => {
/>
{
handleInputChange(
'setting',
@@ -1073,10 +1073,10 @@ const EditChannel = (props) => {
{t('填入模板')}
{
window.open(
- 'https://github.com/Calcium-Ion/new-api/blob/main/docs/channel/other_setting.md',
+ 'https://github.com/QuantumNous/new-api/blob/main/docs/channel/other_setting.md',
);
}}
>
@@ -1146,7 +1146,7 @@ const EditChannel = (props) => {
className="!rounded-lg font-mono"
/>
{
handleInputChange(
'status_code_mapping',
diff --git a/web/src/pages/Chat/index.js b/web/src/pages/Chat/index.js
index bd4e19a1..22c5c1e2 100644
--- a/web/src/pages/Chat/index.js
+++ b/web/src/pages/Chat/index.js
@@ -1,9 +1,11 @@
-import React, { useEffect } from 'react';
+import React from 'react';
import { useTokenKeys } from '../../hooks/useTokenKeys';
-import { Banner, Layout } from '@douyinfe/semi-ui';
+import { Spin } from '@douyinfe/semi-ui';
import { useParams } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
const ChatPage = () => {
+ const { t } = useTranslation();
const { id } = useParams();
const { keys, serverAddress, isLoading } = useTokenKeys(id);
@@ -40,12 +42,17 @@ const ChatPage = () => {
allow='camera;microphone'
/>
) : (
-
-
-
-
-
-
+
+
+
+
+ {t('正在跳转...')}
+
+
);
};