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
6 changes: 5 additions & 1 deletion relay/channel/ali/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions relay/channel/ali/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var ModelList = []string{
"qwq-32b",
"qwen3-235b-a22b",
"text-embedding-v1",
"gte-rerank-v2",
}

var ChannelName = "ali"
27 changes: 27 additions & 0 deletions relay/channel/ali/dto.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ali

import "one-api/dto"

type AliMessage struct {
Content string `json:"content"`
Role string `json:"role"`
Expand Down Expand Up @@ -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
}
83 changes: 83 additions & 0 deletions relay/channel/ali/rerank.go
Original file line number Diff line number Diff line change
@@ -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
}
33 changes: 12 additions & 21 deletions web/src/components/auth/OAuth2Callback.js
Original file line number Diff line number Diff line change
@@ -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();

Expand All @@ -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);
}
Expand All @@ -50,17 +51,7 @@ const OAuth2Callback = (props) => {
sendCode(code, state, 0).then();
}, []);

return (
<div className="flex items-center justify-center min-h-[300px] w-full bg-white rounded-lg shadow p-6">
<Space vertical align="center">
<Spin size="large" spinning={processing}>
<div className="min-h-[200px] min-w-[200px] flex items-center justify-center">
<Typography.Text type="secondary">{prompt}</Typography.Text>
</div>
</Spin>
</Space>
</div>
);
return <Loading prompt={prompt} />;
};

export default OAuth2Callback;
69 changes: 49 additions & 20 deletions web/src/components/auth/PasswordResetConfirm.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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();
Expand All @@ -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;
Expand All @@ -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`, {
Expand All @@ -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);
}
Expand Down Expand Up @@ -94,16 +104,28 @@ const PasswordResetConfirm = () => {
<Title heading={3} className="text-gray-800 dark:text-gray-200">{t('密码重置确认')}</Title>
</div>
<div className="px-2 py-8">
<Form className="space-y-3">
{!isValidResetLink && (
<Banner
type="danger"
description={t('无效的重置链接,请重新发起密码重置请求')}
className="mb-4 !rounded-lg"
closeIcon={null}
/>
)}
<Form
getFormApi={(api) => setFormApi(api)}
initValues={{ email: email || '', newPassword: newPassword || '' }}
className="space-y-4"
>
<Form.Input
field="email"
label={t('邮箱')}
name="email"
size="large"
className="!rounded-md"
value={email}
readOnly
disabled={true}
prefix={<IconMail />}
placeholder={email ? '' : t('等待获取邮箱信息...')}
/>

{newPassword && (
Expand All @@ -113,14 +135,21 @@ const PasswordResetConfirm = () => {
name="newPassword"
size="large"
className="!rounded-md"
value={newPassword}
readOnly
disabled={true}
prefix={<IconLock />}
onClick={(e) => {
e.target.select();
navigator.clipboard.writeText(newPassword);
showNotice(`${t('密码已复制到剪贴板')}: ${newPassword}`);
}}
suffix={
<Button
icon={<IconCopy />}
type="tertiary"
theme="borderless"
onClick={async () => {
await copy(newPassword);
showNotice(`${t('密码已复制到剪贴板:')} ${newPassword}`);
}}
>
{t('复制')}
</Button>
}
/>
)}

Expand All @@ -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('确认重置密码')}
</Button>
</div>
</Form>
Expand Down
5 changes: 4 additions & 1 deletion web/src/components/auth/PasswordResetForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/common/Loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const Loading = ({ prompt: name = '', size = 'large' }) => {
tip={null}
/>
<span className="whitespace-nowrap mt-2 text-center" style={{ color: 'var(--semi-color-primary)' }}>
{name ? t('加载{{name}}中...', { name }) : t('加载中...')}
{name ? t('{{name}}', { name }) : t('加载中...')}
</span>
</div>
</div>
Expand Down
Loading