mirror of
https://github.com/songquanpeng/one-api.git
synced 2026-01-19 05:15:58 +08:00
Compare commits
20 Commits
patch/prox
...
67e05ef74d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67e05ef74d | ||
|
|
8a283fff3b | ||
|
|
05ee77eb35 | ||
|
|
a3cb66661d | ||
|
|
80568f2d87 | ||
|
|
f27224ab8d | ||
|
|
6be2658e9b | ||
|
|
c9d20f3616 | ||
|
|
1106bcabf2 | ||
|
|
f9774698e9 | ||
|
|
2af6f6a166 | ||
|
|
04bb3ef392 | ||
|
|
b4bfa418a8 | ||
|
|
e7e99e558a | ||
|
|
402fcf7f79 | ||
|
|
36039e329e | ||
|
|
c936198ac8 | ||
|
|
296ab013b8 | ||
|
|
5f03c856b4 | ||
|
|
39383e5532 |
61
.github/workflows/docker-image-amd64.yml
vendored
61
.github/workflows/docker-image-amd64.yml
vendored
@@ -1,61 +0,0 @@
|
|||||||
name: Publish Docker image (amd64)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*.*.*'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
name:
|
|
||||||
description: 'reason'
|
|
||||||
required: false
|
|
||||||
jobs:
|
|
||||||
push_to_registries:
|
|
||||||
name: Push Docker image to multiple registries
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Check out the repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Check repository URL
|
|
||||||
run: |
|
|
||||||
REPO_URL=$(git config --get remote.origin.url)
|
|
||||||
if [[ $REPO_URL == *"pro" ]]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Save version info
|
|
||||||
run: |
|
|
||||||
git describe --tags > VERSION
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
justsong/one-api
|
|
||||||
ghcr.io/${{ github.repository }}
|
|
||||||
|
|
||||||
- name: Build and push Docker images
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Publish Docker image (amd64, English)
|
name: Publish Docker image (English)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -34,6 +34,13 @@ jobs:
|
|||||||
- name: Translate
|
- name: Translate
|
||||||
run: |
|
run: |
|
||||||
python ./i18n/translate.py --repository_path . --json_file_path ./i18n/en.json
|
python ./i18n/translate.py --repository_path . --json_file_path ./i18n/en.json
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -51,6 +58,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
name: Publish Docker image (arm64)
|
name: Publish Docker image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- 'v*.*.*'
|
||||||
- '!*-alpha*'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
name:
|
name:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:16 as builder
|
FROM --platform=$BUILDPLATFORM node:16 AS builder
|
||||||
|
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./VERSION .
|
COPY ./VERSION .
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ var PasswordLoginEnabled = true
|
|||||||
var PasswordRegisterEnabled = true
|
var PasswordRegisterEnabled = true
|
||||||
var EmailVerificationEnabled = false
|
var EmailVerificationEnabled = false
|
||||||
var GitHubOAuthEnabled = false
|
var GitHubOAuthEnabled = false
|
||||||
|
var OidcEnabled = false
|
||||||
var WeChatAuthEnabled = false
|
var WeChatAuthEnabled = false
|
||||||
var TurnstileCheckEnabled = false
|
var TurnstileCheckEnabled = false
|
||||||
var RegisterEnabled = true
|
var RegisterEnabled = true
|
||||||
@@ -70,6 +71,12 @@ var GitHubClientSecret = ""
|
|||||||
var LarkClientId = ""
|
var LarkClientId = ""
|
||||||
var LarkClientSecret = ""
|
var LarkClientSecret = ""
|
||||||
|
|
||||||
|
var OidcAppId = ""
|
||||||
|
var OidcAppSecret = ""
|
||||||
|
var OidcAuthorizationEndpoint = ""
|
||||||
|
var OidcTokenEndpoint = ""
|
||||||
|
var OidcUserinfoEndpoint = ""
|
||||||
|
|
||||||
var WeChatServerAddress = ""
|
var WeChatServerAddress = ""
|
||||||
var WeChatServerToken = ""
|
var WeChatServerToken = ""
|
||||||
var WeChatAccountQRCodeImageURL = ""
|
var WeChatAccountQRCodeImageURL = ""
|
||||||
|
|||||||
225
controller/auth/oidc.go
Normal file
225
controller/auth/oidc.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
|
"github.com/songquanpeng/one-api/controller"
|
||||||
|
"github.com/songquanpeng/one-api/model"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OidcResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcUser struct {
|
||||||
|
OpenID string `json:"sub"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOidcUserInfoByCode(code string) (*OidcUser, error) {
|
||||||
|
if code == "" {
|
||||||
|
return nil, errors.New("无效的参数")
|
||||||
|
}
|
||||||
|
values := map[string]string{
|
||||||
|
"client_id": config.OidcAppId,
|
||||||
|
"client_secret": config.OidcAppSecret,
|
||||||
|
"code": code,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"redirect_uri": fmt.Sprintf("%s/oauth/oidc", config.ServerAddress),
|
||||||
|
}
|
||||||
|
jsonData, err := json.Marshal(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", config.OidcTokenEndpoint, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.SysLog(err.Error())
|
||||||
|
return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
var oidcResponse OidcResponse
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&oidcResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err = http.NewRequest("GET", config.OidcUserinfoEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+oidcResponse.AccessToken)
|
||||||
|
res2, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.SysLog(err.Error())
|
||||||
|
return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
|
||||||
|
}
|
||||||
|
var oidcUser OidcUser
|
||||||
|
err = json.NewDecoder(res2.Body).Decode(&oidcUser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oidcUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OidcAuth(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
state := c.Query("state")
|
||||||
|
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "state is empty or not same",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username := session.Get("username")
|
||||||
|
if username != nil {
|
||||||
|
OidcBind(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !config.OidcEnabled {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "管理员未开启通过 OIDC 登录以及注册",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
code := c.Query("code")
|
||||||
|
oidcUser, err := getOidcUserInfoByCode(code)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := model.User{
|
||||||
|
OidcId: oidcUser.OpenID,
|
||||||
|
}
|
||||||
|
if model.IsOidcIdAlreadyTaken(user.OidcId) {
|
||||||
|
err := user.FillUserByOidcId()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if config.RegisterEnabled {
|
||||||
|
user.Email = oidcUser.Email
|
||||||
|
if oidcUser.PreferredUsername != "" {
|
||||||
|
user.Username = oidcUser.PreferredUsername
|
||||||
|
} else {
|
||||||
|
user.Username = "oidc_" + strconv.Itoa(model.GetMaxUserId()+1)
|
||||||
|
}
|
||||||
|
if oidcUser.Name != "" {
|
||||||
|
user.DisplayName = oidcUser.Name
|
||||||
|
} else {
|
||||||
|
user.DisplayName = "OIDC User"
|
||||||
|
}
|
||||||
|
err := user.Insert(0)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "管理员关闭了新用户注册",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Status != model.UserStatusEnabled {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "用户已被封禁",
|
||||||
|
"success": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.SetupLogin(&user, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OidcBind(c *gin.Context) {
|
||||||
|
if !config.OidcEnabled {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "管理员未开启通过 OIDC 登录以及注册",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
code := c.Query("code")
|
||||||
|
oidcUser, err := getOidcUserInfoByCode(code)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := model.User{
|
||||||
|
OidcId: oidcUser.OpenID,
|
||||||
|
}
|
||||||
|
if model.IsOidcIdAlreadyTaken(user.OidcId) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "该 OIDC 账户已被绑定",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session := sessions.Default(c)
|
||||||
|
id := session.Get("id")
|
||||||
|
// id := c.GetInt("id") // critical bug!
|
||||||
|
user.Id = id.(int)
|
||||||
|
err = user.FillUserById()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.OidcId = oidcUser.OpenID
|
||||||
|
err = user.Update(false)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "bind",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -18,24 +18,29 @@ func GetStatus(c *gin.Context) {
|
|||||||
"success": true,
|
"success": true,
|
||||||
"message": "",
|
"message": "",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"version": common.Version,
|
"version": common.Version,
|
||||||
"start_time": common.StartTime,
|
"start_time": common.StartTime,
|
||||||
"email_verification": config.EmailVerificationEnabled,
|
"email_verification": config.EmailVerificationEnabled,
|
||||||
"github_oauth": config.GitHubOAuthEnabled,
|
"github_oauth": config.GitHubOAuthEnabled,
|
||||||
"github_client_id": config.GitHubClientId,
|
"github_client_id": config.GitHubClientId,
|
||||||
"lark_client_id": config.LarkClientId,
|
"lark_client_id": config.LarkClientId,
|
||||||
"system_name": config.SystemName,
|
"system_name": config.SystemName,
|
||||||
"logo": config.Logo,
|
"logo": config.Logo,
|
||||||
"footer_html": config.Footer,
|
"footer_html": config.Footer,
|
||||||
"wechat_qrcode": config.WeChatAccountQRCodeImageURL,
|
"wechat_qrcode": config.WeChatAccountQRCodeImageURL,
|
||||||
"wechat_login": config.WeChatAuthEnabled,
|
"wechat_login": config.WeChatAuthEnabled,
|
||||||
"server_address": config.ServerAddress,
|
"server_address": config.ServerAddress,
|
||||||
"turnstile_check": config.TurnstileCheckEnabled,
|
"turnstile_check": config.TurnstileCheckEnabled,
|
||||||
"turnstile_site_key": config.TurnstileSiteKey,
|
"turnstile_site_key": config.TurnstileSiteKey,
|
||||||
"top_up_link": config.TopUpLink,
|
"top_up_link": config.TopUpLink,
|
||||||
"chat_link": config.ChatLink,
|
"chat_link": config.ChatLink,
|
||||||
"quota_per_unit": config.QuotaPerUnit,
|
"quota_per_unit": config.QuotaPerUnit,
|
||||||
"display_in_currency": config.DisplayInCurrencyEnabled,
|
"display_in_currency": config.DisplayInCurrencyEnabled,
|
||||||
|
"oidc": config.OidcEnabled,
|
||||||
|
"oidc_app_id": config.OidcAppId,
|
||||||
|
"oidc_authorization_endpoint": config.OidcAuthorizationEndpoint,
|
||||||
|
"oidc_token_endpoint": config.OidcTokenEndpoint,
|
||||||
|
"oidc_userinfo_endpoint": config.OidcUserinfoEndpoint,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func InitOptionMap() {
|
|||||||
config.OptionMap["PasswordRegisterEnabled"] = strconv.FormatBool(config.PasswordRegisterEnabled)
|
config.OptionMap["PasswordRegisterEnabled"] = strconv.FormatBool(config.PasswordRegisterEnabled)
|
||||||
config.OptionMap["EmailVerificationEnabled"] = strconv.FormatBool(config.EmailVerificationEnabled)
|
config.OptionMap["EmailVerificationEnabled"] = strconv.FormatBool(config.EmailVerificationEnabled)
|
||||||
config.OptionMap["GitHubOAuthEnabled"] = strconv.FormatBool(config.GitHubOAuthEnabled)
|
config.OptionMap["GitHubOAuthEnabled"] = strconv.FormatBool(config.GitHubOAuthEnabled)
|
||||||
|
config.OptionMap["OidcEnabled"] = strconv.FormatBool(config.OidcEnabled)
|
||||||
config.OptionMap["WeChatAuthEnabled"] = strconv.FormatBool(config.WeChatAuthEnabled)
|
config.OptionMap["WeChatAuthEnabled"] = strconv.FormatBool(config.WeChatAuthEnabled)
|
||||||
config.OptionMap["TurnstileCheckEnabled"] = strconv.FormatBool(config.TurnstileCheckEnabled)
|
config.OptionMap["TurnstileCheckEnabled"] = strconv.FormatBool(config.TurnstileCheckEnabled)
|
||||||
config.OptionMap["RegisterEnabled"] = strconv.FormatBool(config.RegisterEnabled)
|
config.OptionMap["RegisterEnabled"] = strconv.FormatBool(config.RegisterEnabled)
|
||||||
@@ -130,6 +131,8 @@ func updateOptionMap(key string, value string) (err error) {
|
|||||||
config.EmailVerificationEnabled = boolValue
|
config.EmailVerificationEnabled = boolValue
|
||||||
case "GitHubOAuthEnabled":
|
case "GitHubOAuthEnabled":
|
||||||
config.GitHubOAuthEnabled = boolValue
|
config.GitHubOAuthEnabled = boolValue
|
||||||
|
case "OidcEnabled":
|
||||||
|
config.OidcEnabled = boolValue
|
||||||
case "WeChatAuthEnabled":
|
case "WeChatAuthEnabled":
|
||||||
config.WeChatAuthEnabled = boolValue
|
config.WeChatAuthEnabled = boolValue
|
||||||
case "TurnstileCheckEnabled":
|
case "TurnstileCheckEnabled":
|
||||||
@@ -176,6 +179,16 @@ func updateOptionMap(key string, value string) (err error) {
|
|||||||
config.LarkClientId = value
|
config.LarkClientId = value
|
||||||
case "LarkClientSecret":
|
case "LarkClientSecret":
|
||||||
config.LarkClientSecret = value
|
config.LarkClientSecret = value
|
||||||
|
case "OidcAppId":
|
||||||
|
config.OidcAppId = value
|
||||||
|
case "OidcAppSecret":
|
||||||
|
config.OidcAppSecret = value
|
||||||
|
case "OidcAuthorizationEndpoint":
|
||||||
|
config.OidcAuthorizationEndpoint = value
|
||||||
|
case "OidcTokenEndpoint":
|
||||||
|
config.OidcTokenEndpoint = value
|
||||||
|
case "OidcUserinfoEndpoint":
|
||||||
|
config.OidcUserinfoEndpoint = value
|
||||||
case "Footer":
|
case "Footer":
|
||||||
config.Footer = value
|
config.Footer = value
|
||||||
case "SystemName":
|
case "SystemName":
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type User struct {
|
|||||||
GitHubId string `json:"github_id" gorm:"column:github_id;index"`
|
GitHubId string `json:"github_id" gorm:"column:github_id;index"`
|
||||||
WeChatId string `json:"wechat_id" gorm:"column:wechat_id;index"`
|
WeChatId string `json:"wechat_id" gorm:"column:wechat_id;index"`
|
||||||
LarkId string `json:"lark_id" gorm:"column:lark_id;index"`
|
LarkId string `json:"lark_id" gorm:"column:lark_id;index"`
|
||||||
|
OidcId string `json:"oidc_id" gorm:"column:oidc_id;index"`
|
||||||
VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
|
VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
|
||||||
AccessToken string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management
|
AccessToken string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management
|
||||||
Quota int64 `json:"quota" gorm:"bigint;default:0"`
|
Quota int64 `json:"quota" gorm:"bigint;default:0"`
|
||||||
@@ -245,6 +246,14 @@ func (user *User) FillUserByLarkId() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) FillUserByOidcId() error {
|
||||||
|
if user.OidcId == "" {
|
||||||
|
return errors.New("oidc id 为空!")
|
||||||
|
}
|
||||||
|
DB.Where(User{OidcId: user.OidcId}).First(user)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (user *User) FillUserByWeChatId() error {
|
func (user *User) FillUserByWeChatId() error {
|
||||||
if user.WeChatId == "" {
|
if user.WeChatId == "" {
|
||||||
return errors.New("WeChat id 为空!")
|
return errors.New("WeChat id 为空!")
|
||||||
@@ -277,6 +286,10 @@ func IsLarkIdAlreadyTaken(githubId string) bool {
|
|||||||
return DB.Where("lark_id = ?", githubId).Find(&User{}).RowsAffected == 1
|
return DB.Where("lark_id = ?", githubId).Find(&User{}).RowsAffected == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsOidcIdAlreadyTaken(oidcId string) bool {
|
||||||
|
return DB.Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1
|
||||||
|
}
|
||||||
|
|
||||||
func IsUsernameAlreadyTaken(username string) bool {
|
func IsUsernameAlreadyTaken(username string) bool {
|
||||||
return DB.Where("username = ?", username).Find(&User{}).RowsAffected == 1
|
return DB.Where("username = ?", username).Find(&User{}).RowsAffected == 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func GetRequestURL(meta *meta.Meta) (string, error) {
|
func GetRequestURL(meta *meta.Meta) (string, error) {
|
||||||
if meta.Mode == relaymode.ChatCompletions {
|
switch meta.Mode {
|
||||||
|
case relaymode.ChatCompletions:
|
||||||
return fmt.Sprintf("%s/api/v3/chat/completions", meta.BaseURL), nil
|
return fmt.Sprintf("%s/api/v3/chat/completions", meta.BaseURL), nil
|
||||||
|
case relaymode.Embeddings:
|
||||||
|
return fmt.Sprintf("%s/api/v3/embeddings", meta.BaseURL), nil
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("unsupported relay mode %d for doubao", meta.Mode)
|
return "", fmt.Errorf("unsupported relay mode %d for doubao", meta.Mode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ var ModelList = []string{
|
|||||||
"gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613",
|
"gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613",
|
||||||
"gpt-4-turbo-preview", "gpt-4-turbo", "gpt-4-turbo-2024-04-09",
|
"gpt-4-turbo-preview", "gpt-4-turbo", "gpt-4-turbo-2024-04-09",
|
||||||
"gpt-4o", "gpt-4o-2024-05-13",
|
"gpt-4o", "gpt-4o-2024-05-13",
|
||||||
|
"gpt-4o-mini", "gpt-4o-mini-2024-07-18",
|
||||||
"gpt-4-vision-preview",
|
"gpt-4-vision-preview",
|
||||||
"text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large",
|
"text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large",
|
||||||
"text-curie-001", "text-babbage-001", "text-ada-001", "text-davinci-002", "text-davinci-003",
|
"text-curie-001", "text-babbage-001", "text-ada-001", "text-davinci-002", "text-davinci-003",
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func CountTokenMessages(messages []model.Message, model string) int {
|
|||||||
if imageUrl["detail"] != nil {
|
if imageUrl["detail"] != nil {
|
||||||
detail = imageUrl["detail"].(string)
|
detail = imageUrl["detail"].(string)
|
||||||
}
|
}
|
||||||
imageTokens, err := countImageTokens(url, detail)
|
imageTokens, err := countImageTokens(url, detail, model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SysError("error counting image tokens: " + err.Error())
|
logger.SysError("error counting image tokens: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
@@ -134,11 +134,15 @@ const (
|
|||||||
lowDetailCost = 85
|
lowDetailCost = 85
|
||||||
highDetailCostPerTile = 170
|
highDetailCostPerTile = 170
|
||||||
additionalCost = 85
|
additionalCost = 85
|
||||||
|
// gpt-4o-mini cost higher than other model
|
||||||
|
gpt4oMiniLowDetailCost = 2833
|
||||||
|
gpt4oMiniHighDetailCost = 5667
|
||||||
|
gpt4oMiniAdditionalCost = 2833
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://platform.openai.com/docs/guides/vision/calculating-costs
|
// https://platform.openai.com/docs/guides/vision/calculating-costs
|
||||||
// https://github.com/openai/openai-cookbook/blob/05e3f9be4c7a2ae7ecf029a7c32065b024730ebe/examples/How_to_count_tokens_with_tiktoken.ipynb
|
// https://github.com/openai/openai-cookbook/blob/05e3f9be4c7a2ae7ecf029a7c32065b024730ebe/examples/How_to_count_tokens_with_tiktoken.ipynb
|
||||||
func countImageTokens(url string, detail string) (_ int, err error) {
|
func countImageTokens(url string, detail string, model string) (_ int, err error) {
|
||||||
var fetchSize = true
|
var fetchSize = true
|
||||||
var width, height int
|
var width, height int
|
||||||
// Reference: https://platform.openai.com/docs/guides/vision/low-or-high-fidelity-image-understanding
|
// Reference: https://platform.openai.com/docs/guides/vision/low-or-high-fidelity-image-understanding
|
||||||
@@ -172,6 +176,9 @@ func countImageTokens(url string, detail string) (_ int, err error) {
|
|||||||
}
|
}
|
||||||
switch detail {
|
switch detail {
|
||||||
case "low":
|
case "low":
|
||||||
|
if strings.HasPrefix(model, "gpt-4o-mini") {
|
||||||
|
return gpt4oMiniLowDetailCost, nil
|
||||||
|
}
|
||||||
return lowDetailCost, nil
|
return lowDetailCost, nil
|
||||||
case "high":
|
case "high":
|
||||||
if fetchSize {
|
if fetchSize {
|
||||||
@@ -191,6 +198,9 @@ func countImageTokens(url string, detail string) (_ int, err error) {
|
|||||||
height = int(float64(height) * ratio)
|
height = int(float64(height) * ratio)
|
||||||
}
|
}
|
||||||
numSquares := int(math.Ceil(float64(width)/512) * math.Ceil(float64(height)/512))
|
numSquares := int(math.Ceil(float64(width)/512) * math.Ceil(float64(height)/512))
|
||||||
|
if strings.HasPrefix(model, "gpt-4o-mini") {
|
||||||
|
return numSquares*gpt4oMiniHighDetailCost + gpt4oMiniAdditionalCost, nil
|
||||||
|
}
|
||||||
result := numSquares*highDetailCostPerTile + additionalCost
|
result := numSquares*highDetailCostPerTile + additionalCost
|
||||||
return result, nil
|
return result, nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
36
relay/adaptor/siliconflow/constants.go
Normal file
36
relay/adaptor/siliconflow/constants.go
Normal 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",
|
||||||
|
}
|
||||||
@@ -28,15 +28,17 @@ var ModelRatio = map[string]float64{
|
|||||||
"gpt-4-32k": 30,
|
"gpt-4-32k": 30,
|
||||||
"gpt-4-32k-0314": 30,
|
"gpt-4-32k-0314": 30,
|
||||||
"gpt-4-32k-0613": 30,
|
"gpt-4-32k-0613": 30,
|
||||||
"gpt-4-1106-preview": 5, // $0.01 / 1K tokens
|
"gpt-4-1106-preview": 5, // $0.01 / 1K tokens
|
||||||
"gpt-4-0125-preview": 5, // $0.01 / 1K tokens
|
"gpt-4-0125-preview": 5, // $0.01 / 1K tokens
|
||||||
"gpt-4-turbo-preview": 5, // $0.01 / 1K tokens
|
"gpt-4-turbo-preview": 5, // $0.01 / 1K tokens
|
||||||
"gpt-4-turbo": 5, // $0.01 / 1K tokens
|
"gpt-4-turbo": 5, // $0.01 / 1K tokens
|
||||||
"gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens
|
"gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens
|
||||||
"gpt-4o": 2.5, // $0.005 / 1K tokens
|
"gpt-4o": 2.5, // $0.005 / 1K tokens
|
||||||
"gpt-4o-2024-05-13": 2.5, // $0.005 / 1K tokens
|
"gpt-4o-2024-05-13": 2.5, // $0.005 / 1K tokens
|
||||||
"gpt-4-vision-preview": 5, // $0.01 / 1K tokens
|
"gpt-4o-mini": 0.075, // $0.00015 / 1K tokens
|
||||||
"gpt-3.5-turbo": 0.25, // $0.0005 / 1K tokens
|
"gpt-4o-mini-2024-07-18": 0.075, // $0.00015 / 1K tokens
|
||||||
|
"gpt-4-vision-preview": 5, // $0.01 / 1K tokens
|
||||||
|
"gpt-3.5-turbo": 0.25, // $0.0005 / 1K tokens
|
||||||
"gpt-3.5-turbo-0301": 0.75,
|
"gpt-3.5-turbo-0301": 0.75,
|
||||||
"gpt-3.5-turbo-0613": 0.75,
|
"gpt-3.5-turbo-0613": 0.75,
|
||||||
"gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens
|
"gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens
|
||||||
@@ -96,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,
|
||||||
@@ -156,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,
|
||||||
@@ -308,6 +313,9 @@ func GetCompletionRatio(name string, channelType int) float64 {
|
|||||||
return 4.0 / 3.0
|
return 4.0 / 3.0
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(name, "gpt-4") {
|
if strings.HasPrefix(name, "gpt-4") {
|
||||||
|
if strings.HasPrefix(name, "gpt-4o-mini") {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
if strings.HasPrefix(name, "gpt-4-turbo") ||
|
if strings.HasPrefix(name, "gpt-4-turbo") ||
|
||||||
strings.HasPrefix(name, "gpt-4o") ||
|
strings.HasPrefix(name, "gpt-4o") ||
|
||||||
strings.HasSuffix(name, "preview") {
|
strings.HasSuffix(name, "preview") {
|
||||||
|
|||||||
@@ -45,5 +45,6 @@ const (
|
|||||||
Novita
|
Novita
|
||||||
VertextAI
|
VertextAI
|
||||||
Proxy
|
Proxy
|
||||||
|
SiliconFlow
|
||||||
Dummy
|
Dummy
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/songquanpeng/one-api/common/logger"
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
"github.com/songquanpeng/one-api/relay"
|
"github.com/songquanpeng/one-api/relay"
|
||||||
|
"github.com/songquanpeng/one-api/relay/adaptor"
|
||||||
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
||||||
"github.com/songquanpeng/one-api/relay/apitype"
|
"github.com/songquanpeng/one-api/relay/apitype"
|
||||||
"github.com/songquanpeng/one-api/relay/billing"
|
"github.com/songquanpeng/one-api/relay/billing"
|
||||||
@@ -31,9 +32,8 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
|
|||||||
meta.IsStream = textRequest.Stream
|
meta.IsStream = textRequest.Stream
|
||||||
|
|
||||||
// map model name
|
// map model name
|
||||||
var isModelMapped bool
|
|
||||||
meta.OriginModelName = textRequest.Model
|
meta.OriginModelName = textRequest.Model
|
||||||
textRequest.Model, isModelMapped = getMappedModelName(textRequest.Model, meta.ModelMapping)
|
textRequest.Model, _ = getMappedModelName(textRequest.Model, meta.ModelMapping)
|
||||||
meta.ActualModelName = textRequest.Model
|
meta.ActualModelName = textRequest.Model
|
||||||
// get model ratio & group ratio
|
// get model ratio & group ratio
|
||||||
modelRatio := billingratio.GetModelRatio(textRequest.Model, meta.ChannelType)
|
modelRatio := billingratio.GetModelRatio(textRequest.Model, meta.ChannelType)
|
||||||
@@ -55,30 +55,9 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
|
|||||||
adaptor.Init(meta)
|
adaptor.Init(meta)
|
||||||
|
|
||||||
// get request body
|
// get request body
|
||||||
var requestBody io.Reader
|
requestBody, err := getRequestBody(c, meta, textRequest, adaptor)
|
||||||
if meta.APIType == apitype.OpenAI {
|
if err != nil {
|
||||||
// no need to convert request for openai
|
return openai.ErrorWrapper(err, "convert_request_failed", http.StatusInternalServerError)
|
||||||
shouldResetRequestBody := isModelMapped || meta.ChannelType == channeltype.Baichuan // frequency_penalty 0 is not acceptable for baichuan
|
|
||||||
if shouldResetRequestBody {
|
|
||||||
jsonStr, err := json.Marshal(textRequest)
|
|
||||||
if err != nil {
|
|
||||||
return openai.ErrorWrapper(err, "json_marshal_failed", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
requestBody = bytes.NewBuffer(jsonStr)
|
|
||||||
} else {
|
|
||||||
requestBody = c.Request.Body
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
convertedRequest, err := adaptor.ConvertRequest(c, meta.Mode, textRequest)
|
|
||||||
if err != nil {
|
|
||||||
return openai.ErrorWrapper(err, "convert_request_failed", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
jsonData, err := json.Marshal(convertedRequest)
|
|
||||||
if err != nil {
|
|
||||||
return openai.ErrorWrapper(err, "json_marshal_failed", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
logger.Debugf(ctx, "converted request: \n%s", string(jsonData))
|
|
||||||
requestBody = bytes.NewBuffer(jsonData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// do request
|
// do request
|
||||||
@@ -103,3 +82,26 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
|
|||||||
go postConsumeQuota(ctx, usage, meta, textRequest, ratio, preConsumedQuota, modelRatio, groupRatio)
|
go postConsumeQuota(ctx, usage, meta, textRequest, ratio, preConsumedQuota, modelRatio, groupRatio)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRequestBody(c *gin.Context, meta *meta.Meta, textRequest *model.GeneralOpenAIRequest, adaptor adaptor.Adaptor) (io.Reader, error) {
|
||||||
|
if meta.APIType == apitype.OpenAI && meta.OriginModelName == meta.ActualModelName && meta.ChannelType != channeltype.Baichuan {
|
||||||
|
// no need to convert request for openai
|
||||||
|
return c.Request.Body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get request body
|
||||||
|
var requestBody io.Reader
|
||||||
|
convertedRequest, err := adaptor.ConvertRequest(c, meta.Mode, textRequest)
|
||||||
|
if err != nil {
|
||||||
|
logger.Debugf(c.Request.Context(), "converted request failed: %s\n", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jsonData, err := json.Marshal(convertedRequest)
|
||||||
|
if err != nil {
|
||||||
|
logger.Debugf(c.Request.Context(), "converted request json_marshal_failed: %s\n", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logger.Debugf(c.Request.Context(), "converted request: \n%s", string(jsonData))
|
||||||
|
requestBody = bytes.NewBuffer(jsonData)
|
||||||
|
return requestBody, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
|
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
|
||||||
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
||||||
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), auth.GitHubOAuth)
|
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), auth.GitHubOAuth)
|
||||||
|
apiRouter.GET("/oauth/oidc", middleware.CriticalRateLimit(), auth.OidcAuth)
|
||||||
apiRouter.GET("/oauth/lark", middleware.CriticalRateLimit(), auth.LarkOAuth)
|
apiRouter.GET("/oauth/lark", middleware.CriticalRateLimit(), auth.LarkOAuth)
|
||||||
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), auth.GenerateOAuthCode)
|
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), auth.GenerateOAuthCode)
|
||||||
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), auth.WeChatAuth)
|
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), auth.WeChatAuth)
|
||||||
|
|||||||
@@ -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' },
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.3 KiB |
7
web/berry/src/assets/images/icons/oidc.svg
Normal file
7
web/berry/src/assets/images/icons/oidc.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg t="1723135116886" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="10969" width="200" height="200">
|
||||||
|
<path d="M512 960C265 960 64 759 64 512S265 64 512 64s448 201 448 448-201 448-448 448z m0-882.6c-239.7 0-434.6 195-434.6 434.6s195 434.6 434.6 434.6 434.6-195 434.6-434.6S751.7 77.4 512 77.4z"
|
||||||
|
p-id="10970" fill="#2c2c2c" stroke="#2c2c2c" stroke-width="60"></path>
|
||||||
|
<path d="M197.7 512c0-78.3 31.6-98.8 87.2-98.8 56.2 0 87.2 20.5 87.2 98.8s-31 98.8-87.2 98.8c-55.7 0-87.2-20.5-87.2-98.8z m130.4 0c0-46.8-7.8-64.5-43.2-64.5-35.2 0-42.9 17.7-42.9 64.5 0 47.1 7.8 63.7 42.9 63.7 35.4 0 43.2-16.6 43.2-63.7zM409.7 415.9h42.1V608h-42.1V415.9zM653.9 512c0 74.2-37.1 96.1-93.6 96.1h-65.9V415.9h65.9c56.5 0 93.6 16.1 93.6 96.1z m-43.5 0c0-49.3-17.7-60.6-52.3-60.6h-21.6v120.7h21.6c35.4 0 52.3-13.3 52.3-60.1zM686.5 512c0-74.2 36.3-98.8 92.7-98.8 18.3 0 33.2 2.2 44.8 6.4v36.3c-11.9-4.2-26-6.6-42.1-6.6-34.6 0-49.8 15.5-49.8 62.6 0 50.1 15.2 62.6 49.3 62.6 15.8 0 30.2-2.2 44.8-7.5v36c-11.3 4.7-28.5 8-46.8 8-56.1-0.2-92.9-18.7-92.9-99z"
|
||||||
|
p-id="10971" fill="#2c2c2c" stroke="#2c2c2c" stroke-width="20"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -22,7 +22,12 @@ const config = {
|
|||||||
turnstile_site_key: '',
|
turnstile_site_key: '',
|
||||||
version: '',
|
version: '',
|
||||||
wechat_login: false,
|
wechat_login: false,
|
||||||
wechat_qrcode: ''
|
wechat_qrcode: '',
|
||||||
|
oidc: false,
|
||||||
|
oidc_app_id: '',
|
||||||
|
oidc_authorization_endpoint: '',
|
||||||
|
oidc_token_endpoint: '',
|
||||||
|
oidc_userinfo_endpoint: '',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -70,6 +70,28 @@ const useLogin = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const oidcLogin = async (code, state) => {
|
||||||
|
try {
|
||||||
|
const res = await API.get(`/api/oauth/oidc?code=${code}&state=${state}`);
|
||||||
|
const { success, message, data } = res.data;
|
||||||
|
if (success) {
|
||||||
|
if (message === 'bind') {
|
||||||
|
showSuccess('绑定成功!');
|
||||||
|
navigate('/panel');
|
||||||
|
} else {
|
||||||
|
dispatch({ type: LOGIN, payload: data });
|
||||||
|
localStorage.setItem('user', JSON.stringify(data));
|
||||||
|
showSuccess('登录成功!');
|
||||||
|
navigate('/panel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { success, message };
|
||||||
|
} catch (err) {
|
||||||
|
// 请求失败,设置错误信息
|
||||||
|
return { success: false, message: '' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const wechatLogin = async (code) => {
|
const wechatLogin = async (code) => {
|
||||||
try {
|
try {
|
||||||
const res = await API.get(`/api/oauth/wechat?code=${code}`);
|
const res = await API.get(`/api/oauth/wechat?code=${code}`);
|
||||||
@@ -94,7 +116,7 @@ const useLogin = () => {
|
|||||||
navigate('/');
|
navigate('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
return { login, logout, githubLogin, wechatLogin, larkLogin };
|
return { login, logout, githubLogin, wechatLogin, larkLogin,oidcLogin };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useLogin;
|
export default useLogin;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const AuthLogin = Loadable(lazy(() => import('views/Authentication/Auth/Login'))
|
|||||||
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
||||||
const GitHubOAuth = Loadable(lazy(() => import('views/Authentication/Auth/GitHubOAuth')));
|
const GitHubOAuth = Loadable(lazy(() => import('views/Authentication/Auth/GitHubOAuth')));
|
||||||
const LarkOAuth = Loadable(lazy(() => import('views/Authentication/Auth/LarkOAuth')));
|
const LarkOAuth = Loadable(lazy(() => import('views/Authentication/Auth/LarkOAuth')));
|
||||||
|
const OidcOAuth = Loadable(lazy(() => import('views/Authentication/Auth/OidcOAuth')));
|
||||||
const ForgetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ForgetPassword')));
|
const ForgetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ForgetPassword')));
|
||||||
const ResetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ResetPassword')));
|
const ResetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ResetPassword')));
|
||||||
const Home = Loadable(lazy(() => import('views/Home')));
|
const Home = Loadable(lazy(() => import('views/Home')));
|
||||||
@@ -53,6 +54,10 @@ const OtherRoutes = {
|
|||||||
path: '/oauth/lark',
|
path: '/oauth/lark',
|
||||||
element: <LarkOAuth />
|
element: <LarkOAuth />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'oauth/oidc',
|
||||||
|
element: <OidcOAuth />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/404',
|
path: '/404',
|
||||||
element: <NotFoundView />
|
element: <NotFoundView />
|
||||||
|
|||||||
@@ -98,6 +98,21 @@ export async function onLarkOAuthClicked(lark_client_id) {
|
|||||||
window.open(`https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${redirect_uri}&app_id=${lark_client_id}&state=${state}`);
|
window.open(`https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${redirect_uri}&app_id=${lark_client_id}&state=${state}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function onOidcClicked(auth_url, client_id, openInNewTab = false) {
|
||||||
|
const state = await getOAuthState();
|
||||||
|
if (!state) return;
|
||||||
|
const redirect_uri = `${window.location.origin}/oauth/oidc`;
|
||||||
|
const response_type = "code";
|
||||||
|
const scope = "openid profile email";
|
||||||
|
const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`;
|
||||||
|
if (openInNewTab) {
|
||||||
|
window.open(url);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function isAdmin() {
|
export function isAdmin() {
|
||||||
let user = localStorage.getItem('user');
|
let user = localStorage.getItem('user');
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
|
|||||||
94
web/berry/src/views/Authentication/Auth/OidcOAuth.js
Normal file
94
web/berry/src/views/Authentication/Auth/OidcOAuth.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { showError } from 'utils/common';
|
||||||
|
import useLogin from 'hooks/useLogin';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Grid, Stack, Typography, useMediaQuery, CircularProgress } from '@mui/material';
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import AuthWrapper from '../AuthWrapper';
|
||||||
|
import AuthCardWrapper from '../AuthCardWrapper';
|
||||||
|
import Logo from 'ui-component/Logo';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
|
||||||
|
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||||
|
|
||||||
|
const OidcOAuth = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [prompt, setPrompt] = useState('处理中...');
|
||||||
|
const { oidcLogin } = useLogin();
|
||||||
|
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
|
const sendCode = async (code, state, count) => {
|
||||||
|
const { success, message } = await oidcLogin(code, state);
|
||||||
|
if (!success) {
|
||||||
|
if (message) {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
if (count === 0) {
|
||||||
|
setPrompt(`操作失败,重定向至登录界面中...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
navigate('/login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
await sendCode(code, state, count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let code = searchParams.get('code');
|
||||||
|
let state = searchParams.get('state');
|
||||||
|
sendCode(code, state, 0).then();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthWrapper>
|
||||||
|
<Grid container direction="column" justifyContent="flex-end">
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Grid container justifyContent="center" alignItems="center" sx={{ minHeight: 'calc(100vh - 136px)' }}>
|
||||||
|
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||||
|
<AuthCardWrapper>
|
||||||
|
<Grid container spacing={2} alignItems="center" justifyContent="center">
|
||||||
|
<Grid item sx={{ mb: 3 }}>
|
||||||
|
<Link to="#">
|
||||||
|
<Logo />
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Grid container direction={matchDownSM ? 'column-reverse' : 'row'} alignItems="center" justifyContent="center">
|
||||||
|
<Grid item>
|
||||||
|
<Stack alignItems="center" justifyContent="center" spacing={1}>
|
||||||
|
<Typography color={theme.palette.primary.main} gutterBottom variant={matchDownSM ? 'h3' : 'h2'}>
|
||||||
|
OIDC 登录
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} container direction="column" justifyContent="center" alignItems="center" style={{ height: '200px' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography variant="h3" paddingTop={'20px'}>
|
||||||
|
{prompt}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</AuthCardWrapper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</AuthWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OidcOAuth;
|
||||||
@@ -36,7 +36,8 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|||||||
import Github from 'assets/images/icons/github.svg';
|
import Github from 'assets/images/icons/github.svg';
|
||||||
import Wechat from 'assets/images/icons/wechat.svg';
|
import Wechat from 'assets/images/icons/wechat.svg';
|
||||||
import Lark from 'assets/images/icons/lark.svg';
|
import Lark from 'assets/images/icons/lark.svg';
|
||||||
import { onGitHubOAuthClicked, onLarkOAuthClicked } from 'utils/common';
|
import OIDC from 'assets/images/icons/oidc.svg';
|
||||||
|
import { onGitHubOAuthClicked, onLarkOAuthClicked, onOidcClicked } from 'utils/common';
|
||||||
|
|
||||||
// ============================|| FIREBASE - LOGIN ||============================ //
|
// ============================|| FIREBASE - LOGIN ||============================ //
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ const LoginForm = ({ ...others }) => {
|
|||||||
// const [checked, setChecked] = useState(true);
|
// const [checked, setChecked] = useState(true);
|
||||||
|
|
||||||
let tripartiteLogin = false;
|
let tripartiteLogin = false;
|
||||||
if (siteInfo.github_oauth || siteInfo.wechat_login || siteInfo.lark_client_id) {
|
if (siteInfo.github_oauth || siteInfo.wechat_login || siteInfo.lark_client_id || siteInfo.oidc) {
|
||||||
tripartiteLogin = true;
|
tripartiteLogin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +146,29 @@ const LoginForm = ({ ...others }) => {
|
|||||||
</AnimateButton>
|
</AnimateButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
{siteInfo.oidc && (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<AnimateButton>
|
||||||
|
<Button
|
||||||
|
disableElevation
|
||||||
|
fullWidth
|
||||||
|
onClick={() => onOidcClicked(siteInfo.oidc_authorization_endpoint,siteInfo.oidc_app_id)}
|
||||||
|
size="large"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
color: 'grey.700',
|
||||||
|
backgroundColor: theme.palette.grey[50],
|
||||||
|
borderColor: theme.palette.grey[100]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<img src={OIDC} alt="Lark" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
|
||||||
|
</Box>
|
||||||
|
使用 OIDC 登录
|
||||||
|
</Button>
|
||||||
|
</AnimateButton>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import SubCard from 'ui-component/cards/SubCard';
|
|||||||
import { IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
|
import { IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
|
||||||
import Label from 'ui-component/Label';
|
import Label from 'ui-component/Label';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { showError, showSuccess } from 'utils/common';
|
import { onOidcClicked, showError, showSuccess } from 'utils/common';
|
||||||
import { onGitHubOAuthClicked, onLarkOAuthClicked, copy } from 'utils/common';
|
import { onGitHubOAuthClicked, onLarkOAuthClicked, copy } from 'utils/common';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import WechatModal from 'views/Authentication/AuthForms/WechatModal';
|
import WechatModal from 'views/Authentication/AuthForms/WechatModal';
|
||||||
@@ -28,6 +28,7 @@ import { useSelector } from 'react-redux';
|
|||||||
import EmailModal from './component/EmailModal';
|
import EmailModal from './component/EmailModal';
|
||||||
import Turnstile from 'react-turnstile';
|
import Turnstile from 'react-turnstile';
|
||||||
import { ReactComponent as Lark } from 'assets/images/icons/lark.svg';
|
import { ReactComponent as Lark } from 'assets/images/icons/lark.svg';
|
||||||
|
import { ReactComponent as OIDC } from 'assets/images/icons/oidc.svg';
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
username: Yup.string().required('用户名 不能为空').min(3, '用户名 不能小于 3 个字符'),
|
username: Yup.string().required('用户名 不能为空').min(3, '用户名 不能小于 3 个字符'),
|
||||||
@@ -123,6 +124,15 @@ export default function Profile() {
|
|||||||
loadUser().then();
|
loadUser().then();
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
|
function getOidcId(){
|
||||||
|
if (!inputs.oidc_id) return '';
|
||||||
|
let oidc_id = inputs.oidc_id;
|
||||||
|
if (inputs.oidc_id.length > 8) {
|
||||||
|
oidc_id = inputs.oidc_id.slice(0, 6) + '...' + inputs.oidc_id.slice(-6);
|
||||||
|
}
|
||||||
|
return oidc_id;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UserCard>
|
<UserCard>
|
||||||
@@ -141,6 +151,9 @@ export default function Profile() {
|
|||||||
<Label variant="ghost" color={inputs.lark_id ? 'primary' : 'default'}>
|
<Label variant="ghost" color={inputs.lark_id ? 'primary' : 'default'}>
|
||||||
<SvgIcon component={Lark} inheritViewBox="0 0 24 24" /> {inputs.lark_id || '未绑定'}
|
<SvgIcon component={Lark} inheritViewBox="0 0 24 24" /> {inputs.lark_id || '未绑定'}
|
||||||
</Label>
|
</Label>
|
||||||
|
<Label variant="ghost" color={inputs.oidc_id ? 'primary' : 'default'}>
|
||||||
|
<SvgIcon component={OIDC} inheritViewBox="0 0 24 24" /> {getOidcId() || '未绑定'}
|
||||||
|
</Label>
|
||||||
</Stack>
|
</Stack>
|
||||||
<SubCard title="个人信息">
|
<SubCard title="个人信息">
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
@@ -216,6 +229,13 @@ export default function Profile() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
{status.oidc && !inputs.oidc_id && (
|
||||||
|
<Grid xs={12} md={4}>
|
||||||
|
<Button variant="contained" onClick={() => onOidcClicked(status.oidc_authorization_endpoint,status.oidc_app_id,true)}>
|
||||||
|
绑定 OIDC 账号
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
<Grid xs={12} md={4}>
|
<Grid xs={12} md={4}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ const SystemSetting = () => {
|
|||||||
GitHubClientSecret: '',
|
GitHubClientSecret: '',
|
||||||
LarkClientId: '',
|
LarkClientId: '',
|
||||||
LarkClientSecret: '',
|
LarkClientSecret: '',
|
||||||
|
OidcEnabled: '',
|
||||||
|
OidcAppId: '',
|
||||||
|
OidcAppSecret: '',
|
||||||
|
OidcAuthorizationEndpoint: '',
|
||||||
|
OidcTokenEndpoint: '',
|
||||||
|
OidcUserinfoEndpoint: '',
|
||||||
Notice: '',
|
Notice: '',
|
||||||
SMTPServer: '',
|
SMTPServer: '',
|
||||||
SMTPPort: '',
|
SMTPPort: '',
|
||||||
@@ -94,6 +100,7 @@ const SystemSetting = () => {
|
|||||||
case 'TurnstileCheckEnabled':
|
case 'TurnstileCheckEnabled':
|
||||||
case 'EmailDomainRestrictionEnabled':
|
case 'EmailDomainRestrictionEnabled':
|
||||||
case 'RegisterEnabled':
|
case 'RegisterEnabled':
|
||||||
|
case 'OidcEnabled':
|
||||||
value = inputs[key] === 'true' ? 'false' : 'true';
|
value = inputs[key] === 'true' ? 'false' : 'true';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -142,8 +149,14 @@ const SystemSetting = () => {
|
|||||||
name === 'MessagePusherAddress' ||
|
name === 'MessagePusherAddress' ||
|
||||||
name === 'MessagePusherToken' ||
|
name === 'MessagePusherToken' ||
|
||||||
name === 'LarkClientId' ||
|
name === 'LarkClientId' ||
|
||||||
name === 'LarkClientSecret'
|
name === 'LarkClientSecret' ||
|
||||||
) {
|
name === 'OidcAppId' ||
|
||||||
|
name === 'OidcAppSecret' ||
|
||||||
|
name === 'OidcAuthorizationEndpoint' ||
|
||||||
|
name === 'OidcTokenEndpoint' ||
|
||||||
|
name === 'OidcUserinfoEndpoint'
|
||||||
|
)
|
||||||
|
{
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
} else {
|
} else {
|
||||||
await updateOption(name, value);
|
await updateOption(name, value);
|
||||||
@@ -225,6 +238,32 @@ const SystemSetting = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitOidc = async () => {
|
||||||
|
const OidcConfig = {
|
||||||
|
OidcAppId: inputs.OidcAppId,
|
||||||
|
OidcAppSecret: inputs.OidcAppSecret,
|
||||||
|
OidcAuthorizationEndpoint: inputs.OidcAuthorizationEndpoint,
|
||||||
|
OidcTokenEndpoint: inputs.OidcTokenEndpoint,
|
||||||
|
OidcUserinfoEndpoint: inputs.OidcUserinfoEndpoint
|
||||||
|
};
|
||||||
|
console.log(OidcConfig);
|
||||||
|
if (originInputs['OidcAppId'] !== inputs.OidcAppId) {
|
||||||
|
await updateOption('OidcAppId', inputs.OidcAppId);
|
||||||
|
}
|
||||||
|
if (originInputs['OidcAppSecret'] !== inputs.OidcAppSecret && inputs.OidcAppSecret !== '') {
|
||||||
|
await updateOption('OidcAppSecret', inputs.OidcAppSecret);
|
||||||
|
}
|
||||||
|
if (originInputs['OidcAuthorizationEndpoint'] !== inputs.OidcAuthorizationEndpoint) {
|
||||||
|
await updateOption('OidcAuthorizationEndpoint', inputs.OidcAuthorizationEndpoint);
|
||||||
|
}
|
||||||
|
if (originInputs['OidcTokenEndpoint'] !== inputs.OidcTokenEndpoint) {
|
||||||
|
await updateOption('OidcTokenEndpoint', inputs.OidcTokenEndpoint);
|
||||||
|
}
|
||||||
|
if (originInputs['OidcUserinfoEndpoint'] !== inputs.OidcUserinfoEndpoint) {
|
||||||
|
await updateOption('OidcUserinfoEndpoint', inputs.OidcUserinfoEndpoint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
@@ -291,6 +330,12 @@ const SystemSetting = () => {
|
|||||||
control={<Checkbox checked={inputs.GitHubOAuthEnabled === 'true'} onChange={handleInputChange} name="GitHubOAuthEnabled" />}
|
control={<Checkbox checked={inputs.GitHubOAuthEnabled === 'true'} onChange={handleInputChange} name="GitHubOAuthEnabled" />}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid xs={12} md={3}>
|
||||||
|
<FormControlLabel
|
||||||
|
label="允许通过 Oidc 登录 & 注册"
|
||||||
|
control={<Checkbox checked={inputs.OidcEnabled === 'true'} onChange={handleInputChange} name="OidcEnabled" />}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
<Grid xs={12} md={3}>
|
<Grid xs={12} md={3}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
label="允许通过微信登录 & 注册"
|
label="允许通过微信登录 & 注册"
|
||||||
@@ -616,6 +661,100 @@ const SystemSetting = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</SubCard>
|
</SubCard>
|
||||||
|
|
||||||
|
<SubCard
|
||||||
|
title="配置 OIDC"
|
||||||
|
subTitle={
|
||||||
|
<span>
|
||||||
|
用以支持通过 OIDC 登录,例如 Okta、Auth0 等兼容 OIDC 协议的 IdP
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid container spacing={ { xs: 3, sm: 2, md: 4 } }>
|
||||||
|
<Grid xs={ 12 } md={ 12 }>
|
||||||
|
<Alert severity="info" sx={ { wordWrap: 'break-word' } }>
|
||||||
|
主页链接填 <code>{ inputs.ServerAddress }</code>
|
||||||
|
,重定向 URL 填 <code>{ `${ inputs.ServerAddress }/oauth/oidc` }</code>
|
||||||
|
</Alert>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={ 12 } md={ 6 }>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor="OidcAppId">App ID</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="OidcAppId"
|
||||||
|
name="OidcAppId"
|
||||||
|
value={ inputs.OidcAppId || '' }
|
||||||
|
onChange={ handleInputChange }
|
||||||
|
label="App ID"
|
||||||
|
placeholder="输入 OAuth 2.0 的 App ID"
|
||||||
|
disabled={ loading }
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={ 12 } md={ 6 }>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor="OidcAppSecret">App Secret</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="OidcAppSecret"
|
||||||
|
name="OidcAppSecret"
|
||||||
|
value={ inputs.OidcAppSecret || '' }
|
||||||
|
onChange={ handleInputChange }
|
||||||
|
label="App Secret"
|
||||||
|
placeholder="敏感信息不会发送到前端显示"
|
||||||
|
disabled={ loading }
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={ 12 } md={ 6 }>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor="OidcAuthorizationEndpoint">授权地址</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="OidcAuthorizationEndpoint"
|
||||||
|
name="OidcAuthorizationEndpoint"
|
||||||
|
value={ inputs.OidcAuthorizationEndpoint || '' }
|
||||||
|
onChange={ handleInputChange }
|
||||||
|
label="授权地址"
|
||||||
|
placeholder="输入 OAuth 2.0 的 授权地址"
|
||||||
|
disabled={ loading }
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={ 12 } md={ 6 }>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor="OidcTokenEndpoint">认证地址</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="OidcTokenEndpoint"
|
||||||
|
name="OidcTokenEndpoint"
|
||||||
|
value={ inputs.OidcTokenEndpoint || '' }
|
||||||
|
onChange={ handleInputChange }
|
||||||
|
label="认证地址"
|
||||||
|
placeholder="输入 OAuth 2.0 的 认证地址"
|
||||||
|
disabled={ loading }
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={ 12 } md={ 6 }>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor="OidcUserinfoEndpoint">用户地址</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="OidcUserinfoEndpoint"
|
||||||
|
name="OidcUserinfoEndpoint"
|
||||||
|
value={ inputs.OidcUserinfoEndpoint || '' }
|
||||||
|
onChange={ handleInputChange }
|
||||||
|
label="认证地址"
|
||||||
|
placeholder="输入 OAuth 2.0 的 认证地址"
|
||||||
|
disabled={ loading }
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={ 12 }>
|
||||||
|
<Button variant="contained" onClick={ submitOidc }>
|
||||||
|
保存第三方 OAuth 2.0 设置
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</SubCard>
|
||||||
|
|
||||||
<SubCard
|
<SubCard
|
||||||
title="配置 Message Pusher"
|
title="配置 Message Pusher"
|
||||||
subTitle={
|
subTitle={
|
||||||
|
|||||||
@@ -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' },
|
||||||
|
|||||||
Reference in New Issue
Block a user