Compare commits

...

9 Commits

Author SHA1 Message Date
jinjianming
791f14f25a Merge 3f06711501 into f9774698e9 2024-08-07 10:12:59 +08:00
longkeyy
f9774698e9 feat: synchronize with the official release of the groq model (#1677)
update groq add gemma2-9b-it llama3.1 family fixup price k/token -> m/token
2024-08-06 23:51:08 +08:00
TAKO
2af6f6a166 feat: add Cloudflare New Free Model Llama 3.1 8b (#1703) 2024-08-06 23:49:48 +08:00
MotorBottle
04bb3ef392 feat: add Max Tokens and Context Window Setting Options for Ollama Channel (#1694)
* Update main.go with max_tokens param

* Update model.go with max_tokens param

* Update model.go

* Update main.go

* Update main.go

* Adds num_ctx param for Ollama Channel

* Added num_ctx param for ollama adapter

* Added num_ctx param for ollama adapter

* Improved data process logic
2024-08-06 23:44:37 +08:00
longkeyy
b4bfa418a8 feat: update gemini model and price (#1705) 2024-08-06 23:43:33 +08:00
SLKun
e7e99e558a feat: update Ollama embedding API to latest version with multi-text embedding support (#1715) 2024-08-06 23:43:20 +08:00
Shenghang Tsai
402fcf7f79 feat: add SiliconFlow (#1717)
* Add SiliconFlow

* Update README.md

* Update README.md

* Update channel.constants.js

* Update ChannelConstants.js

* Update channel.constants.js

* Update ChannelConstants.js

* Update compatible.go

* Update README.md
2024-08-06 23:42:25 +08:00
Junyan Qin
36039e329e docs: update introduction for QChatGPT (#1707) 2024-08-06 23:33:43 +08:00
jinjianmingming
3f06711501 feat: berry主题发送邮箱验证码添加loading效果和倒计时,修复多次点击导致发送多个验证码问题 2024-07-19 11:43:06 +08:00
17 changed files with 334 additions and 230 deletions

View File

@@ -89,6 +89,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
+ [x] [DeepL](https://www.deepl.com/) + [x] [DeepL](https://www.deepl.com/)
+ [x] [together.ai](https://www.together.ai/) + [x] [together.ai](https://www.together.ai/)
+ [x] [novita.ai](https://www.novita.ai/) + [x] [novita.ai](https://www.novita.ai/)
+ [x] [硅基流动 SiliconCloud](https://siliconflow.cn/siliconcloud)
2. 支持配置镜像以及众多[第三方代理服务](https://iamazing.cn/page/openai-api-third-party-services)。 2. 支持配置镜像以及众多[第三方代理服务](https://iamazing.cn/page/openai-api-third-party-services)。
3. 支持通过**负载均衡**的方式访问多个渠道。 3. 支持通过**负载均衡**的方式访问多个渠道。
4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。 4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
@@ -251,9 +252,9 @@ docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://ope
#### QChatGPT - QQ机器人 #### QChatGPT - QQ机器人
项目主页https://github.com/RockChinQ/QChatGPT 项目主页https://github.com/RockChinQ/QChatGPT
根据文档完成部署后,在`config.py`设置配置项`openai_config`的`reverse_proxy`为 One API 后端地址,设置`api_key`为 One API 生成的key并在配置项`completion_api_params`的`model`参数设置为 One API 支持的模型名称。 根据[文档](https://qchatgpt.rockchin.top)完成部署后,在 `data/provider.json`设置`requester.openai-chat-completions.base-url`为 One API 实例地址,并填写 API Key 到 `keys.openai` 组中,设置 `model` 为要使用的模型名称。
可安装 [Switcher 插件](https://github.com/RockChinQ/Switcher)在运行时切换所使用的模型。 运行期间可以通过`!model`命令查看、切换可用模型。
### 部署到第三方平台 ### 部署到第三方平台
<details> <details>

View File

@@ -1,6 +1,7 @@
package cloudflare package cloudflare
var ModelList = []string{ var ModelList = []string{
"@cf/meta/llama-3.1-8b-instruct",
"@cf/meta/llama-2-7b-chat-fp16", "@cf/meta/llama-2-7b-chat-fp16",
"@cf/meta/llama-2-7b-chat-int8", "@cf/meta/llama-2-7b-chat-int8",
"@cf/mistral/mistral-7b-instruct-v0.1", "@cf/mistral/mistral-7b-instruct-v0.1",

View File

@@ -3,6 +3,5 @@ package gemini
// https://ai.google.dev/models/gemini // https://ai.google.dev/models/gemini
var ModelList = []string{ var ModelList = []string{
"gemini-pro", "gemini-1.0-pro-001", "gemini-1.5-pro", "gemini-pro", "gemini-1.0-pro", "gemini-1.5-flash", "gemini-1.5-pro", "text-embedding-004", "aqa",
"gemini-pro-vision", "gemini-1.0-pro-vision-001", "embedding-001", "text-embedding-004",
} }

View File

@@ -4,9 +4,14 @@ package groq
var ModelList = []string{ var ModelList = []string{
"gemma-7b-it", "gemma-7b-it",
"llama2-7b-2048",
"llama2-70b-4096",
"mixtral-8x7b-32768", "mixtral-8x7b-32768",
"llama3-8b-8192", "llama3-8b-8192",
"llama3-70b-8192", "llama3-70b-8192",
"gemma2-9b-it",
"llama-3.1-405b-reasoning",
"llama-3.1-70b-versatile",
"llama-3.1-8b-instant",
"llama3-groq-70b-8192-tool-use-preview",
"llama3-groq-8b-8192-tool-use-preview",
"whisper-large-v3",
} }

View File

@@ -24,7 +24,7 @@ func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
// https://github.com/ollama/ollama/blob/main/docs/api.md // https://github.com/ollama/ollama/blob/main/docs/api.md
fullRequestURL := fmt.Sprintf("%s/api/chat", meta.BaseURL) fullRequestURL := fmt.Sprintf("%s/api/chat", meta.BaseURL)
if meta.Mode == relaymode.Embeddings { if meta.Mode == relaymode.Embeddings {
fullRequestURL = fmt.Sprintf("%s/api/embeddings", meta.BaseURL) fullRequestURL = fmt.Sprintf("%s/api/embed", meta.BaseURL)
} }
return fullRequestURL, nil return fullRequestURL, nil
} }

View File

@@ -31,6 +31,8 @@ func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
TopP: request.TopP, TopP: request.TopP,
FrequencyPenalty: request.FrequencyPenalty, FrequencyPenalty: request.FrequencyPenalty,
PresencePenalty: request.PresencePenalty, PresencePenalty: request.PresencePenalty,
NumPredict: request.MaxTokens,
NumCtx: request.NumCtx,
}, },
Stream: request.Stream, Stream: request.Stream,
} }
@@ -118,8 +120,10 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
common.SetEventStreamHeaders(c) common.SetEventStreamHeaders(c)
for scanner.Scan() { for scanner.Scan() {
data := strings.TrimPrefix(scanner.Text(), "}") data := scanner.Text()
data = data + "}" if strings.HasPrefix(data, "}") {
data = strings.TrimPrefix(data, "}") + "}"
}
var ollamaResponse ChatResponse var ollamaResponse ChatResponse
err := json.Unmarshal([]byte(data), &ollamaResponse) err := json.Unmarshal([]byte(data), &ollamaResponse)
@@ -157,8 +161,15 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *EmbeddingRequest { func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *EmbeddingRequest {
return &EmbeddingRequest{ return &EmbeddingRequest{
Model: request.Model, Model: request.Model,
Prompt: strings.Join(request.ParseInput(), " "), Input: request.ParseInput(),
Options: &Options{
Seed: int(request.Seed),
Temperature: request.Temperature,
TopP: request.TopP,
FrequencyPenalty: request.FrequencyPenalty,
PresencePenalty: request.PresencePenalty,
},
} }
} }
@@ -201,15 +212,17 @@ func embeddingResponseOllama2OpenAI(response *EmbeddingResponse) *openai.Embeddi
openAIEmbeddingResponse := openai.EmbeddingResponse{ openAIEmbeddingResponse := openai.EmbeddingResponse{
Object: "list", Object: "list",
Data: make([]openai.EmbeddingResponseItem, 0, 1), Data: make([]openai.EmbeddingResponseItem, 0, 1),
Model: "text-embedding-v1", Model: response.Model,
Usage: model.Usage{TotalTokens: 0}, Usage: model.Usage{TotalTokens: 0},
} }
openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, openai.EmbeddingResponseItem{ for i, embedding := range response.Embeddings {
Object: `embedding`, openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, openai.EmbeddingResponseItem{
Index: 0, Object: `embedding`,
Embedding: response.Embedding, Index: i,
}) Embedding: embedding,
})
}
return &openAIEmbeddingResponse return &openAIEmbeddingResponse
} }

View File

@@ -7,6 +7,8 @@ type Options struct {
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"` FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"` PresencePenalty float64 `json:"presence_penalty,omitempty"`
NumPredict int `json:"num_predict,omitempty"`
NumCtx int `json:"num_ctx,omitempty"`
} }
type Message struct { type Message struct {
@@ -37,11 +39,15 @@ type ChatResponse struct {
} }
type EmbeddingRequest struct { type EmbeddingRequest struct {
Model string `json:"model"` Model string `json:"model"`
Prompt string `json:"prompt"` Input []string `json:"input"`
// Truncate bool `json:"truncate,omitempty"`
Options *Options `json:"options,omitempty"`
// KeepAlive string `json:"keep_alive,omitempty"`
} }
type EmbeddingResponse struct { type EmbeddingResponse struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
Embedding []float64 `json:"embedding,omitempty"` Model string `json:"model"`
Embeddings [][]float64 `json:"embeddings"`
} }

View File

@@ -13,6 +13,7 @@ import (
"github.com/songquanpeng/one-api/relay/adaptor/novita" "github.com/songquanpeng/one-api/relay/adaptor/novita"
"github.com/songquanpeng/one-api/relay/adaptor/stepfun" "github.com/songquanpeng/one-api/relay/adaptor/stepfun"
"github.com/songquanpeng/one-api/relay/adaptor/togetherai" "github.com/songquanpeng/one-api/relay/adaptor/togetherai"
"github.com/songquanpeng/one-api/relay/adaptor/siliconflow"
"github.com/songquanpeng/one-api/relay/channeltype" "github.com/songquanpeng/one-api/relay/channeltype"
) )
@@ -30,6 +31,7 @@ var CompatibleChannels = []int{
channeltype.DeepSeek, channeltype.DeepSeek,
channeltype.TogetherAI, channeltype.TogetherAI,
channeltype.Novita, channeltype.Novita,
channeltype.SiliconFlow,
} }
func GetCompatibleChannelMeta(channelType int) (string, []string) { func GetCompatibleChannelMeta(channelType int) (string, []string) {
@@ -60,6 +62,8 @@ func GetCompatibleChannelMeta(channelType int) (string, []string) {
return "doubao", doubao.ModelList return "doubao", doubao.ModelList
case channeltype.Novita: case channeltype.Novita:
return "novita", novita.ModelList return "novita", novita.ModelList
case channeltype.SiliconFlow:
return "siliconflow", siliconflow.ModelList
default: default:
return "openai", ModelList return "openai", ModelList
} }

View File

@@ -0,0 +1,36 @@
package siliconflow
// https://docs.siliconflow.cn/docs/getting-started
var ModelList = []string{
"deepseek-ai/deepseek-llm-67b-chat",
"Qwen/Qwen1.5-14B-Chat",
"Qwen/Qwen1.5-7B-Chat",
"Qwen/Qwen1.5-110B-Chat",
"Qwen/Qwen1.5-32B-Chat",
"01-ai/Yi-1.5-6B-Chat",
"01-ai/Yi-1.5-9B-Chat-16K",
"01-ai/Yi-1.5-34B-Chat-16K",
"THUDM/chatglm3-6b",
"deepseek-ai/DeepSeek-V2-Chat",
"THUDM/glm-4-9b-chat",
"Qwen/Qwen2-72B-Instruct",
"Qwen/Qwen2-7B-Instruct",
"Qwen/Qwen2-57B-A14B-Instruct",
"deepseek-ai/DeepSeek-Coder-V2-Instruct",
"Qwen/Qwen2-1.5B-Instruct",
"internlm/internlm2_5-7b-chat",
"BAAI/bge-large-en-v1.5",
"BAAI/bge-large-zh-v1.5",
"Pro/Qwen/Qwen2-7B-Instruct",
"Pro/Qwen/Qwen2-1.5B-Instruct",
"Pro/Qwen/Qwen1.5-7B-Chat",
"Pro/THUDM/glm-4-9b-chat",
"Pro/THUDM/chatglm3-6b",
"Pro/01-ai/Yi-1.5-9B-Chat-16K",
"Pro/01-ai/Yi-1.5-6B-Chat",
"Pro/google/gemma-2-9b-it",
"Pro/internlm/internlm2_5-7b-chat",
"Pro/meta-llama/Meta-Llama-3-8B-Instruct",
"Pro/mistralai/Mistral-7B-Instruct-v0.2",
}

View File

@@ -98,12 +98,11 @@ var ModelRatio = map[string]float64{
"bge-large-en": 0.002 * RMB, "bge-large-en": 0.002 * RMB,
"tao-8k": 0.002 * RMB, "tao-8k": 0.002 * RMB,
// https://ai.google.dev/pricing // https://ai.google.dev/pricing
"PaLM-2": 1, "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
"gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens "gemini-1.0-pro": 1,
"gemini-pro-vision": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens "gemini-1.5-flash": 1,
"gemini-1.0-pro-vision-001": 1, "gemini-1.5-pro": 1,
"gemini-1.0-pro-001": 1, "aqa": 1,
"gemini-1.5-pro": 1,
// https://open.bigmodel.cn/pricing // https://open.bigmodel.cn/pricing
"glm-4": 0.1 * RMB, "glm-4": 0.1 * RMB,
"glm-4v": 0.1 * RMB, "glm-4v": 0.1 * RMB,
@@ -158,12 +157,16 @@ var ModelRatio = map[string]float64{
"mistral-large-latest": 8.0 / 1000 * USD, "mistral-large-latest": 8.0 / 1000 * USD,
"mistral-embed": 0.1 / 1000 * USD, "mistral-embed": 0.1 / 1000 * USD,
// https://wow.groq.com/#:~:text=inquiries%C2%A0here.-,Model,-Current%20Speed // https://wow.groq.com/#:~:text=inquiries%C2%A0here.-,Model,-Current%20Speed
"llama3-70b-8192": 0.59 / 1000 * USD, "gemma-7b-it": 0.07 / 1000000 * USD,
"mixtral-8x7b-32768": 0.27 / 1000 * USD, "mixtral-8x7b-32768": 0.24 / 1000000 * USD,
"llama3-8b-8192": 0.05 / 1000 * USD, "llama3-8b-8192": 0.05 / 1000000 * USD,
"gemma-7b-it": 0.1 / 1000 * USD, "llama3-70b-8192": 0.59 / 1000000 * USD,
"llama2-70b-4096": 0.64 / 1000 * USD, "gemma2-9b-it": 0.20 / 1000000 * USD,
"llama2-7b-2048": 0.1 / 1000 * USD, "llama-3.1-405b-reasoning": 0.89 / 1000000 * USD,
"llama-3.1-70b-versatile": 0.59 / 1000000 * USD,
"llama-3.1-8b-instant": 0.05 / 1000000 * USD,
"llama3-groq-70b-8192-tool-use-preview": 0.89 / 1000000 * USD,
"llama3-groq-8b-8192-tool-use-preview": 0.19 / 1000000 * USD,
// https://platform.lingyiwanwu.com/docs#-计费单元 // https://platform.lingyiwanwu.com/docs#-计费单元
"yi-34b-chat-0205": 2.5 / 1000 * RMB, "yi-34b-chat-0205": 2.5 / 1000 * RMB,
"yi-34b-chat-200k": 12.0 / 1000 * RMB, "yi-34b-chat-200k": 12.0 / 1000 * RMB,

View File

@@ -45,5 +45,6 @@ const (
Novita Novita
VertextAI VertextAI
Proxy Proxy
SiliconFlow
Dummy Dummy
) )

View File

@@ -45,6 +45,7 @@ var ChannelBaseURLs = []string{
"https://api.novita.ai/v3/openai", // 41 "https://api.novita.ai/v3/openai", // 41
"", // 42 "", // 42
"", // 43 "", // 43
"https://api.siliconflow.cn", // 44
} }
func init() { func init() {

View File

@@ -29,6 +29,7 @@ type GeneralOpenAIRequest struct {
Dimensions int `json:"dimensions,omitempty"` Dimensions int `json:"dimensions,omitempty"`
Instruction string `json:"instruction,omitempty"` Instruction string `json:"instruction,omitempty"`
Size string `json:"size,omitempty"` Size string `json:"size,omitempty"`
NumCtx int `json:"num_ctx,omitempty"`
} }
func (r GeneralOpenAIRequest) ParseInput() []string { func (r GeneralOpenAIRequest) ParseInput() []string {

View File

@@ -29,6 +29,7 @@ export const CHANNEL_OPTIONS = [
{ key: 39, text: 'together.ai', value: 39, color: 'blue' }, { key: 39, text: 'together.ai', value: 39, color: 'blue' },
{ key: 42, text: 'VertexAI', value: 42, color: 'blue' }, { key: 42, text: 'VertexAI', value: 42, color: 'blue' },
{ key: 43, text: 'Proxy', value: 43, color: 'blue' }, { key: 43, text: 'Proxy', value: 43, color: 'blue' },
{ key: 44, text: 'SiliconFlow', value: 44, color: 'blue' },
{ key: 8, text: '自定义渠道', value: 8, color: 'pink' }, { key: 8, text: '自定义渠道', value: 8, color: 'pink' },
{ key: 22, text: '知识库FastGPT', value: 22, color: 'blue' }, { key: 22, text: '知识库FastGPT', value: 22, color: 'blue' },
{ key: 21, text: '知识库AI Proxy', value: 21, color: 'purple' }, { key: 21, text: '知识库AI Proxy', value: 21, color: 'purple' },

View File

@@ -173,6 +173,12 @@ export const CHANNEL_OPTIONS = {
value: 43, value: 43,
color: 'primary' color: 'primary'
}, },
44: {
key: 44,
text: 'SiliconFlow',
value: 44,
color: 'primary'
},
41: { 41: {
key: 41, key: 41,
text: 'Novita', text: 'Novita',

View File

@@ -3,13 +3,13 @@ import { useSelector } from 'react-redux';
import useRegister from 'hooks/useRegister'; import useRegister from 'hooks/useRegister';
import Turnstile from 'react-turnstile'; import Turnstile from 'react-turnstile';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
// import { useSelector } from 'react-redux';
// material-ui // material-ui
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import { import {
Box, Box,
Button, Button,
CircularProgress,
FormControl, FormControl,
FormHelperText, FormHelperText,
Grid, Grid,
@@ -50,6 +50,9 @@ const RegisterForm = ({ ...others }) => {
const [strength, setStrength] = useState(0); const [strength, setStrength] = useState(0);
const [level, setLevel] = useState(); const [level, setLevel] = useState();
const [timer, setTimer] = useState(0);
const [loading, setLoading] = useState(false);
const handleClickShowPassword = () => { const handleClickShowPassword = () => {
setShowPassword(!showPassword); setShowPassword(!showPassword);
}; };
@@ -74,11 +77,17 @@ const RegisterForm = ({ ...others }) => {
return; return;
} }
setLoading(true); // Start loading
const { success, message } = await sendVerificationCode(email, turnstileToken); const { success, message } = await sendVerificationCode(email, turnstileToken);
setLoading(false); // Stop loading
if (!success) { if (!success) {
showError(message); showError(message);
return; return;
} }
setTimer(60); // Start the 60-second timer
}; };
useEffect(() => { useEffect(() => {
@@ -94,217 +103,233 @@ const RegisterForm = ({ ...others }) => {
} }
}, [siteInfo]); }, [siteInfo]);
useEffect(() => {
let interval;
if (timer > 0) {
interval = setInterval(() => {
setTimer((prevTimer) => prevTimer - 1);
}, 1000);
}
return () => clearInterval(interval);
}, [timer]);
return ( return (
<> <>
<Formik <Formik
initialValues={{ initialValues={{
username: '', username: '',
password: '', password: '',
confirmPassword: '', confirmPassword: '',
email: showEmailVerification ? '' : undefined, email: showEmailVerification ? '' : undefined,
verification_code: showEmailVerification ? '' : undefined, verification_code: showEmailVerification ? '' : undefined,
submit: null submit: null
}} }}
validationSchema={Yup.object().shape({ validationSchema={Yup.object().shape({
username: Yup.string().max(255).required('用户名是必填项'), username: Yup.string().max(255).required('用户名是必填项'),
password: Yup.string().max(255).required('密码是必填项'), password: Yup.string().max(255).required('密码是必填项'),
confirmPassword: Yup.string() confirmPassword: Yup.string()
.required('确认密码是必填项') .required('确认密码是必填项')
.oneOf([Yup.ref('password'), null], '两次输入的密码不一致'), .oneOf([Yup.ref('password'), null], '两次输入的密码不一致'),
email: showEmailVerification ? Yup.string().email('必须是有效的Email地址').max(255).required('Email是必填项') : Yup.mixed(), email: showEmailVerification ? Yup.string().email('必须是有效的Email地址').max(255).required('Email是必填项') : Yup.mixed(),
verification_code: showEmailVerification ? Yup.string().max(255).required('验证码是必填项') : Yup.mixed() verification_code: showEmailVerification ? Yup.string().max(255).required('验证码是必填项') : Yup.mixed()
})} })}
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => { onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
if (turnstileEnabled && turnstileToken === '') { if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试Turnstile 正在检查用户环境!'); showInfo('请稍后几秒重试Turnstile 正在检查用户环境!');
setSubmitting(false); setSubmitting(false);
return; return;
} }
const { success, message } = await register(values, turnstileToken); const { success, message } = await register(values, turnstileToken);
if (success) { if (success) {
setStatus({ success: true }); setStatus({ success: true });
} else { } else {
setStatus({ success: false }); setStatus({ success: false });
if (message) { if (message) {
setErrors({ submit: message }); setErrors({ submit: message });
}
}
}}
>
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
<form noValidate onSubmit={handleSubmit} {...others}>
<FormControl fullWidth error={Boolean(touched.username && errors.username)} sx={{ ...theme.typography.customInput }}>
<InputLabel htmlFor="outlined-adornment-username-register">用户名</InputLabel>
<OutlinedInput
id="outlined-adornment-username-register"
type="text"
value={values.username}
name="username"
onBlur={handleBlur}
onChange={handleChange}
inputProps={{ autoComplete: 'username' }}
/>
{touched.username && errors.username && (
<FormHelperText error id="standard-weight-helper-text--register">
{errors.username}
</FormHelperText>
)}
</FormControl>
<FormControl fullWidth error={Boolean(touched.password && errors.password)} sx={{ ...theme.typography.customInput }}>
<InputLabel htmlFor="outlined-adornment-password-register">密码</InputLabel>
<OutlinedInput
id="outlined-adornment-password-register"
type={showPassword ? 'text' : 'password'}
value={values.password}
name="password"
label="Password"
onBlur={handleBlur}
onChange={(e) => {
handleChange(e);
changePassword(e.target.value);
}}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
size="large"
color={'primary'}
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
} }
inputProps={{}} }
/> }}
{touched.password && errors.password && ( >
<FormHelperText error id="standard-weight-helper-text-password-register"> {({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
{errors.password} <form noValidate onSubmit={handleSubmit} {...others}>
</FormHelperText> <FormControl fullWidth error={Boolean(touched.username && errors.username)} sx={{ ...theme.typography.customInput }}>
)} <InputLabel htmlFor="outlined-adornment-username-register">用户名</InputLabel>
</FormControl>
<FormControl
fullWidth
error={Boolean(touched.confirmPassword && errors.confirmPassword)}
sx={{ ...theme.typography.customInput }}
>
<InputLabel htmlFor="outlined-adornment-confirm-password-register">确认密码</InputLabel>
<OutlinedInput
id="outlined-adornment-confirm-password-register"
type={showPassword ? 'text' : 'password'}
value={values.confirmPassword}
name="confirmPassword"
label="Confirm Password"
onBlur={handleBlur}
onChange={handleChange}
inputProps={{}}
/>
{touched.confirmPassword && errors.confirmPassword && (
<FormHelperText error id="standard-weight-helper-text-confirm-password-register">
{errors.confirmPassword}
</FormHelperText>
)}
</FormControl>
{strength !== 0 && (
<FormControl fullWidth>
<Box sx={{ mb: 2 }}>
<Grid container spacing={2} alignItems="center">
<Grid item>
<Box style={{ backgroundColor: level?.color }} sx={{ width: 85, height: 8, borderRadius: '7px' }} />
</Grid>
<Grid item>
<Typography variant="subtitle1" fontSize="0.75rem">
{level?.label}
</Typography>
</Grid>
</Grid>
</Box>
</FormControl>
)}
{showEmailVerification && (
<>
<FormControl fullWidth error={Boolean(touched.email && errors.email)} sx={{ ...theme.typography.customInput }}>
<InputLabel htmlFor="outlined-adornment-email-register">Email</InputLabel>
<OutlinedInput <OutlinedInput
id="outlined-adornment-email-register" id="outlined-adornment-username-register"
type="text" type="text"
value={values.email} value={values.username}
name="email" name="username"
onBlur={handleBlur} onBlur={handleBlur}
onChange={handleChange} onChange={handleChange}
endAdornment={ inputProps={{ autoComplete: 'username' }}
<InputAdornment position="end">
<Button variant="contained" color="primary" onClick={() => handleSendCode(values.email)}>
发送验证码
</Button>
</InputAdornment>
}
inputProps={{}}
/> />
{touched.email && errors.email && ( {touched.username && errors.username && (
<FormHelperText error id="standard-weight-helper-text--register"> <FormHelperText error id="standard-weight-helper-text--register">
{errors.email} {errors.username}
</FormHelperText> </FormHelperText>
)}
</FormControl>
<FormControl fullWidth error={Boolean(touched.password && errors.password)} sx={{ ...theme.typography.customInput }}>
<InputLabel htmlFor="outlined-adornment-password-register">密码</InputLabel>
<OutlinedInput
id="outlined-adornment-password-register"
type={showPassword ? 'text' : 'password'}
value={values.password}
name="password"
label="Password"
onBlur={handleBlur}
onChange={(e) => {
handleChange(e);
changePassword(e.target.value);
}}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
size="large"
color={'primary'}
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
inputProps={{}}
/>
{touched.password && errors.password && (
<FormHelperText error id="standard-weight-helper-text-password-register">
{errors.password}
</FormHelperText>
)} )}
</FormControl> </FormControl>
<FormControl <FormControl
fullWidth fullWidth
error={Boolean(touched.verification_code && errors.verification_code)} error={Boolean(touched.confirmPassword && errors.confirmPassword)}
sx={{ ...theme.typography.customInput }} sx={{ ...theme.typography.customInput }}
> >
<InputLabel htmlFor="outlined-adornment-verification_code-register">验证</InputLabel> <InputLabel htmlFor="outlined-adornment-confirm-password-register">确认密</InputLabel>
<OutlinedInput <OutlinedInput
id="outlined-adornment-verification_code-register" id="outlined-adornment-confirm-password-register"
type="text" type={showPassword ? 'text' : 'password'}
value={values.verification_code} value={values.confirmPassword}
name="verification_code" name="confirmPassword"
onBlur={handleBlur} label="Confirm Password"
onChange={handleChange} onBlur={handleBlur}
inputProps={{}} onChange={handleChange}
inputProps={{}}
/> />
{touched.verification_code && errors.verification_code && ( {touched.confirmPassword && errors.confirmPassword && (
<FormHelperText error id="standard-weight-helper-text--register"> <FormHelperText error id="standard-weight-helper-text-confirm-password-register">
{errors.verification_code} {errors.confirmPassword}
</FormHelperText> </FormHelperText>
)} )}
</FormControl> </FormControl>
</>
)}
{errors.submit && ( {strength !== 0 && (
<Box sx={{ mt: 3 }}> <FormControl fullWidth>
<FormHelperText error>{errors.submit}</FormHelperText> <Box sx={{ mb: 2 }}>
</Box> <Grid container spacing={2} alignItems="center">
)} <Grid item>
{turnstileEnabled ? ( <Box style={{ backgroundColor: level?.color }} sx={{ width: 85, height: 8, borderRadius: '7px' }} />
<Turnstile </Grid>
sitekey={turnstileSiteKey} <Grid item>
onVerify={(token) => { <Typography variant="subtitle1" fontSize="0.75rem">
setTurnstileToken(token); {level?.label}
}} </Typography>
/> </Grid>
) : ( </Grid>
<></> </Box>
)} </FormControl>
)}
<Box sx={{ mt: 2 }}> {showEmailVerification && (
<AnimateButton> <>
<Button disableElevation disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary"> <FormControl fullWidth error={Boolean(touched.email && errors.email)} sx={{ ...theme.typography.customInput }}>
注册 <InputLabel htmlFor="outlined-adornment-email-register">Email</InputLabel>
</Button> <OutlinedInput
</AnimateButton> id="outlined-adornment-email-register"
</Box> type="text"
</form> value={values.email}
)} name="email"
</Formik> onBlur={handleBlur}
</> onChange={handleChange}
endAdornment={
<InputAdornment position="end">
<Button
variant="contained"
color="primary"
onClick={() => handleSendCode(values.email)}
disabled={timer > 0 || loading}
>
{loading ? <CircularProgress size={24} /> : timer > 0 ? `${timer}s` : '发送验证码'}
</Button>
</InputAdornment>
}
inputProps={{}}
/>
{touched.email && errors.email && (
<FormHelperText error id="standard-weight-helper-text--register">
{errors.email}
</FormHelperText>
)}
</FormControl>
<FormControl
fullWidth
error={Boolean(touched.verification_code && errors.verification_code)}
sx={{ ...theme.typography.customInput }}
>
<InputLabel htmlFor="outlined-adornment-verification_code-register">验证码</InputLabel>
<OutlinedInput
id="outlined-adornment-verification_code-register"
type="text"
value={values.verification_code}
name="verification_code"
onBlur={handleBlur}
onChange={handleChange}
inputProps={{}}
/>
{touched.verification_code && errors.verification_code && (
<FormHelperText error id="standard-weight-helper-text--register">
{errors.verification_code}
</FormHelperText>
)}
</FormControl>
</>
)}
{errors.submit && (
<Box sx={{ mt: 3 }}>
<FormHelperText error>{errors.submit}</FormHelperText>
</Box>
)}
{turnstileEnabled ? (
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
}}
/>
) : (
<></>
)}
<Box sx={{ mt: 2 }}>
<AnimateButton>
<Button disableElevation disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary">
注册
</Button>
</AnimateButton>
</Box>
</form>
)}
</Formik>
</>
); );
}; };
export default RegisterForm; export default RegisterForm;

View File

@@ -29,6 +29,7 @@ export const CHANNEL_OPTIONS = [
{ key: 39, text: 'together.ai', value: 39, color: 'blue' }, { key: 39, text: 'together.ai', value: 39, color: 'blue' },
{ key: 42, text: 'VertexAI', value: 42, color: 'blue' }, { key: 42, text: 'VertexAI', value: 42, color: 'blue' },
{ key: 43, text: 'Proxy', value: 43, color: 'blue' }, { key: 43, text: 'Proxy', value: 43, color: 'blue' },
{ key: 44, text: 'SiliconFlow', value: 44, color: 'blue' },
{ key: 8, text: '自定义渠道', value: 8, color: 'pink' }, { key: 8, text: '自定义渠道', value: 8, color: 'pink' },
{ key: 22, text: '知识库FastGPT', value: 22, color: 'blue' }, { key: 22, text: '知识库FastGPT', value: 22, color: 'blue' },
{ key: 21, text: '知识库AI Proxy', value: 21, color: 'purple' }, { key: 21, text: '知识库AI Proxy', value: 21, color: 'purple' },