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/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('密码重置确认')}
-
+ {!isValidResetLink && ( + + )} + setFormApi(api)} + initValues={{ email: email || '', newPassword: newPassword || '' }} + className="space-y-4" + > } + 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={ + + } /> )} @@ -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/i18n/locales/en.json b/web/src/i18n/locales/en.json index a3a05acd..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",