mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-01 22:33:41 +08:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66e02a4bcf | ||
|
|
cf564f36fa | ||
|
|
43f8d5fd92 | ||
|
|
15d9a0c177 | ||
|
|
17125448cb | ||
|
|
a891a3e64f | ||
|
|
fd72565011 | ||
|
|
4b9756b257 | ||
|
|
a6ae20ed54 | ||
|
|
617149d731 | ||
|
|
edd2c4f6e9 | ||
|
|
481c4ebf49 | ||
|
|
203471d7a9 | ||
|
|
7ad6f7d99d | ||
|
|
68abcd48ab | ||
|
|
0c175b4e44 | ||
|
|
4139a7036f | ||
|
|
4e31c3991d | ||
|
|
5b8a826cf9 | ||
|
|
02da0b51f8 | ||
|
|
35cfebee12 | ||
|
|
0e088f7c3e | ||
|
|
f61d326721 | ||
|
|
74b06b643a | ||
|
|
ccf7709e23 | ||
|
|
d592e2c8b8 | ||
|
|
b520b54625 | ||
|
|
81c5901123 | ||
|
|
abc53cb208 |
@@ -56,6 +56,9 @@ var GitHubClientSecret = ""
|
|||||||
|
|
||||||
var DiscordClientId = ""
|
var DiscordClientId = ""
|
||||||
var DiscordClientSecret = ""
|
var DiscordClientSecret = ""
|
||||||
|
var DiscordGuildId = ""
|
||||||
|
var DiscordAllowJoiningGuild = "false"
|
||||||
|
var DiscordBotToken = ""
|
||||||
|
|
||||||
var WeChatServerAddress = ""
|
var WeChatServerAddress = ""
|
||||||
var WeChatServerToken = ""
|
var WeChatServerToken = ""
|
||||||
@@ -158,6 +161,7 @@ const (
|
|||||||
|
|
||||||
// Reserve engineering for public projects
|
// Reserve engineering for public projects
|
||||||
ChannelTypeChatGPTWeb = 14 // Chanzhaoyu/chatgpt-web
|
ChannelTypeChatGPTWeb = 14 // Chanzhaoyu/chatgpt-web
|
||||||
|
ChannelTypeChatbotUI = 15 // mckaywrigley/chatbot-ui
|
||||||
)
|
)
|
||||||
|
|
||||||
var ChannelBaseURLs = []string{
|
var ChannelBaseURLs = []string{
|
||||||
@@ -178,4 +182,5 @@ var ChannelBaseURLs = []string{
|
|||||||
|
|
||||||
// Reserve engineering for public projects
|
// Reserve engineering for public projects
|
||||||
"", // 14 // Chanzhaoyu/chatgpt-web
|
"", // 14 // Chanzhaoyu/chatgpt-web
|
||||||
|
"", // 15 // mckaywrigley/chatbot-ui
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func GetSubscription(c *gin.Context) {
|
func GetSubscription(c *gin.Context) {
|
||||||
var quota int
|
var remainQuota int
|
||||||
|
var usedQuota int
|
||||||
var err error
|
var err error
|
||||||
var expirationDate int64
|
var expirationDate int64
|
||||||
|
|
||||||
@@ -18,10 +19,14 @@ func GetSubscription(c *gin.Context) {
|
|||||||
expirationDate = token.ExpiredTime
|
expirationDate = token.ExpiredTime
|
||||||
|
|
||||||
if common.DisplayTokenStatEnabled {
|
if common.DisplayTokenStatEnabled {
|
||||||
quota = token.RemainQuota
|
tokenId := c.GetInt("token_id")
|
||||||
|
token, err = model.GetTokenById(tokenId)
|
||||||
|
remainQuota = token.RemainQuota
|
||||||
|
usedQuota = token.UsedQuota
|
||||||
} else {
|
} else {
|
||||||
userId := c.GetInt("id")
|
userId := c.GetInt("id")
|
||||||
quota, err = model.GetUserQuota(userId)
|
remainQuota, err = model.GetUserQuota(userId)
|
||||||
|
usedQuota, err = model.GetUserUsedQuota(userId)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
openAIError := OpenAIError{
|
openAIError := OpenAIError{
|
||||||
@@ -33,6 +38,7 @@ func GetSubscription(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
quota := remainQuota + usedQuota
|
||||||
amount := float64(quota)
|
amount := float64(quota)
|
||||||
if common.DisplayInCurrencyEnabled {
|
if common.DisplayInCurrencyEnabled {
|
||||||
amount /= common.QuotaPerUnit
|
amount /= common.QuotaPerUnit
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func formatFloat(input float64) float64 {
|
||||||
|
if input == float64(int64(input)) {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return float64(int64(input*10)) / 10
|
||||||
|
}
|
||||||
|
|
||||||
func testChannel(channel *model.Channel, request ChatRequest) error {
|
func testChannel(channel *model.Channel, request ChatRequest) error {
|
||||||
switch channel.Type {
|
switch channel.Type {
|
||||||
case common.ChannelTypeAzure:
|
case common.ChannelTypeAzure:
|
||||||
@@ -32,7 +39,10 @@ func testChannel(channel *model.Channel, request ChatRequest) error {
|
|||||||
if channel.BaseURL != "" {
|
if channel.BaseURL != "" {
|
||||||
requestURL = channel.BaseURL
|
requestURL = channel.BaseURL
|
||||||
}
|
}
|
||||||
requestURL += "/api/chat-process"
|
} else if channel.Type == common.ChannelTypeChatbotUI {
|
||||||
|
if channel.BaseURL != "" {
|
||||||
|
requestURL = channel.BaseURL
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if channel.BaseURL != "" {
|
if channel.BaseURL != "" {
|
||||||
requestURL = channel.BaseURL
|
requestURL = channel.BaseURL
|
||||||
@@ -66,16 +76,49 @@ func testChannel(channel *model.Channel, request ChatRequest) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct json data without adding escape character
|
// Construct json data without adding escape character
|
||||||
map1 := map[string]string{
|
map1 := make(map[string]interface{})
|
||||||
"prompt": prompt,
|
|
||||||
"systemMessage": systemMessage.Content,
|
map1["prompt"] = prompt
|
||||||
"temperature": strconv.FormatFloat(request.Temperature, 'f', 2, 64),
|
map1["systemMessage"] = systemMessage.Content
|
||||||
"top_p": strconv.FormatFloat(request.TopP, 'f', 2, 64),
|
|
||||||
|
if request.Temperature != 0 {
|
||||||
|
map1["temperature"] = formatFloat(request.Temperature)
|
||||||
|
}
|
||||||
|
if request.TopP != 0 {
|
||||||
|
map1["top_p"] = formatFloat(request.TopP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert map to json string
|
// Convert map to json string
|
||||||
jsonData, err = json.Marshal(map1)
|
jsonData, err = json.Marshal(map1)
|
||||||
|
} else if channel.Type == common.ChannelTypeChatbotUI {
|
||||||
|
// Get system message from Message json, Role == "system"
|
||||||
|
var systemMessage string
|
||||||
|
|
||||||
|
for _, message := range request.Messages {
|
||||||
|
if message.Role == "system" {
|
||||||
|
systemMessage = message.Content
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct json data without adding escape character
|
||||||
|
map1 := make(map[string]interface{})
|
||||||
|
|
||||||
|
map1["prompt"] = systemMessage
|
||||||
|
map1["temperature"] = formatFloat(request.Temperature)
|
||||||
|
map1["key"] = ""
|
||||||
|
map1["messages"] = request.Messages
|
||||||
|
map1["model"] = map[string]interface{}{
|
||||||
|
"id": request.Model,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to json string
|
||||||
|
jsonData, err = json.Marshal(map1)
|
||||||
|
|
||||||
|
//Print jsoinData to console
|
||||||
|
log.Println(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,6 +146,20 @@ func testChannel(channel *model.Channel, request ChatRequest) error {
|
|||||||
req.Header.Set("X-Remote-Addr", ip)
|
req.Header.Set("X-Remote-Addr", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
custom_http_headers := channel.CustomHttpHeaders
|
||||||
|
if custom_http_headers != "" {
|
||||||
|
var custom_http_headers_map map[string]string
|
||||||
|
err := json.Unmarshal([]byte(custom_http_headers), &custom_http_headers_map)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range custom_http_headers_map {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,32 +177,55 @@ func testChannel(channel *model.Channel, request ChatRequest) error {
|
|||||||
return errors.New("error response: " + strconv.Itoa(resp.StatusCode))
|
return errors.New("error response: " + strconv.Itoa(resp.StatusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
var done = false
|
|
||||||
var streamResponseText = ""
|
var streamResponseText = ""
|
||||||
|
|
||||||
scanner := bufio.NewScanner(resp.Body)
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
||||||
if atEOF && len(data) == 0 {
|
if channel.Type != common.ChannelTypeChatGPTWeb && channel.Type != common.ChannelTypeChatbotUI {
|
||||||
|
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
if atEOF && len(data) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if i := strings.Index(string(data), "\n"); i >= 0 {
|
||||||
|
return i + 2, data[0:i], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
|
||||||
return 0, nil, nil
|
return 0, nil, nil
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if i := strings.Index(string(data), "\n\n"); i >= 0 {
|
|
||||||
return i + 2, data[0:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if atEOF {
|
|
||||||
return len(data), data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil, nil
|
|
||||||
})
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
data := scanner.Text()
|
data := scanner.Text()
|
||||||
if len(data) < 6 { // must be something wrong!
|
if len(data) < 6 { // must be something wrong!
|
||||||
common.SysError("invalid stream response: " + data)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if channel.Type != common.ChannelTypeChatGPTWeb {
|
|
||||||
|
if channel.Type == common.ChannelTypeChatGPTWeb {
|
||||||
|
var chatResponse ChatGptWebChatResponse
|
||||||
|
err = json.Unmarshal([]byte(data), &chatResponse)
|
||||||
|
if err != nil {
|
||||||
|
// Print the body in string
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(resp.Body)
|
||||||
|
common.SysError("error unmarshalling chat response: " + err.Error() + " " + buf.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if response role is assistant and contains delta, append the content to streamResponseText
|
||||||
|
if chatResponse.Role == "assistant" && chatResponse.Detail != nil {
|
||||||
|
for _, choice := range chatResponse.Detail.Choices {
|
||||||
|
streamResponseText += choice.Delta.Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if channel.Type == common.ChannelTypeChatbotUI {
|
||||||
|
streamResponseText += data
|
||||||
|
} else if channel.Type != common.ChannelTypeChatGPTWeb {
|
||||||
// If data has event: event content inside, remove it, it can be prefix or inside the data
|
// If data has event: event content inside, remove it, it can be prefix or inside the data
|
||||||
if strings.HasPrefix(data, "event:") || strings.Contains(data, "event:") {
|
if strings.HasPrefix(data, "event:") || strings.Contains(data, "event:") {
|
||||||
// Remove event: event in the front or back
|
// Remove event: event in the front or back
|
||||||
@@ -182,46 +262,15 @@ func testChannel(channel *model.Channel, request ChatRequest) error {
|
|||||||
for _, choice := range streamResponse.Choices {
|
for _, choice := range streamResponse.Choices {
|
||||||
streamResponseText += choice.Delta.Content
|
streamResponseText += choice.Delta.Content
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
done = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} else if channel.Type == common.ChannelTypeChatGPTWeb {
|
|
||||||
// data may contain multiple json objects, so we need to split them
|
|
||||||
// they are "{....}{....}{....}" or "{....}\n{....}\n{....}" or "{....}"
|
|
||||||
|
|
||||||
// remove all spaces and newlines outside of json objects
|
|
||||||
jsonObjs := strings.Split(data, "\n") // Split the data into multiple JSON objects
|
|
||||||
for _, jsonObj := range jsonObjs {
|
|
||||||
if jsonObj == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var chatResponse ChatGptWebChatResponse
|
|
||||||
err = json.Unmarshal([]byte(jsonObj), &chatResponse)
|
|
||||||
if err != nil {
|
|
||||||
// Print the body in string
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.ReadFrom(resp.Body)
|
|
||||||
common.SysError("error unmarshalling chat response: " + err.Error() + " " + buf.String())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if response role is assistant and contains delta, append the content to streamResponseText
|
|
||||||
if chatResponse.Role == "assistant" && chatResponse.Detail != nil {
|
|
||||||
for _, choice := range chatResponse.Detail.Choices {
|
|
||||||
log.Print(choice.Delta.Content)
|
|
||||||
streamResponseText += choice.Delta.Content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Check if streaming is complete and streamResponseText is populated
|
// Check if streaming is complete and streamResponseText is populated
|
||||||
if streamResponseText == "" || !done && channel.Type != common.ChannelTypeChatGPTWeb {
|
if streamResponseText == "" {
|
||||||
return errors.New("Streaming not complete")
|
return errors.New("Streaming not complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +284,7 @@ func buildTestRequest() *ChatRequest {
|
|||||||
}
|
}
|
||||||
testMessage := Message{
|
testMessage := Message{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Content: "say hi word only",
|
Content: "Hello ChatGPT!",
|
||||||
}
|
}
|
||||||
testRequest.Messages = append(testRequest.Messages, testMessage)
|
testRequest.Messages = append(testRequest.Messages, testMessage)
|
||||||
return testRequest
|
return testRequest
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
"one-api/model"
|
"one-api/model"
|
||||||
@@ -36,7 +38,7 @@ func getDiscordUserInfoByCode(codeFromURLParamaters string, host string) (*Disco
|
|||||||
ClientID: common.DiscordClientId,
|
ClientID: common.DiscordClientId,
|
||||||
ClientSecret: common.DiscordClientSecret,
|
ClientSecret: common.DiscordClientSecret,
|
||||||
RedirectURI: fmt.Sprintf("https://%s/oauth/discord", host),
|
RedirectURI: fmt.Sprintf("https://%s/oauth/discord", host),
|
||||||
Scopes: []string{disgoauth.ScopeIdentify, disgoauth.ScopeEmail},
|
Scopes: []string{disgoauth.ScopeIdentify, disgoauth.ScopeEmail, disgoauth.ScopeGuilds, disgoauth.ScopeGuildsJoin},
|
||||||
})
|
})
|
||||||
|
|
||||||
accessToken, _ := dc.GetOnlyAccessToken(codeFromURLParamaters)
|
accessToken, _ := dc.GetOnlyAccessToken(codeFromURLParamaters)
|
||||||
@@ -58,6 +60,46 @@ func getDiscordUserInfoByCode(codeFromURLParamaters string, host string) (*Disco
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add guild member.
|
||||||
|
if common.DiscordGuildId != "" && discordUser.Id != "" && common.DiscordBotToken != "" && common.DiscordAllowJoiningGuild == "true" {
|
||||||
|
url := fmt.Sprintf("https://discord.com/api/guilds/%s/members/%s", common.DiscordGuildId, discordUser.Id)
|
||||||
|
|
||||||
|
// Set JSON
|
||||||
|
map1 := map[string]interface{}{
|
||||||
|
// accessToken remove "Bearer "
|
||||||
|
"access_token": string(accessToken[7:]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to JSON
|
||||||
|
jsonData, _ := json.Marshal(map1)
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("PUT", url, bytes.NewBuffer(jsonData))
|
||||||
|
|
||||||
|
// Set Header
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bot %s", common.DiscordBotToken))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Create a new HTTP Client
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
log.Print(resp.StatusCode)
|
||||||
|
|
||||||
|
if err != nil || (resp.StatusCode != 200 && resp.StatusCode != 201 && resp.StatusCode != 204) {
|
||||||
|
// Print content
|
||||||
|
stringBuff := new(bytes.Buffer)
|
||||||
|
stringBuff.ReadFrom(resp.Body)
|
||||||
|
|
||||||
|
// Print error
|
||||||
|
fmt.Println("Error: ", stringBuff.String())
|
||||||
|
|
||||||
|
return nil, errors.New("You must join the discord server first or be verified member to be able to login!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the response body
|
||||||
|
defer resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if discordUser.Username == "" {
|
if discordUser.Username == "" {
|
||||||
return nil, errors.New("Invalid return value, user field is empty, please try again later!")
|
return nil, errors.New("Invalid return value, user field is empty, please try again later!")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,25 +15,27 @@ 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": common.EmailVerificationEnabled,
|
"email_verification": common.EmailVerificationEnabled,
|
||||||
"github_oauth": common.GitHubOAuthEnabled,
|
"github_oauth": common.GitHubOAuthEnabled,
|
||||||
"github_client_id": common.GitHubClientId,
|
"github_client_id": common.GitHubClientId,
|
||||||
"discord_oauth": common.DiscordOAuthEnabled,
|
"discord_oauth": common.DiscordOAuthEnabled,
|
||||||
"discord_client_id": common.DiscordClientId,
|
"discord_client_id": common.DiscordClientId,
|
||||||
"system_name": common.SystemName,
|
"discord_guild_id": common.DiscordGuildId,
|
||||||
"logo": common.Logo,
|
"discord_allow_joining_guild": common.DiscordAllowJoiningGuild,
|
||||||
"footer_html": common.Footer,
|
"system_name": common.SystemName,
|
||||||
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
|
"logo": common.Logo,
|
||||||
"wechat_login": common.WeChatAuthEnabled,
|
"footer_html": common.Footer,
|
||||||
"server_address": common.ServerAddress,
|
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
|
||||||
"turnstile_check": common.TurnstileCheckEnabled,
|
"wechat_login": common.WeChatAuthEnabled,
|
||||||
"turnstile_site_key": common.TurnstileSiteKey,
|
"server_address": common.ServerAddress,
|
||||||
"top_up_link": common.TopUpLink,
|
"turnstile_check": common.TurnstileCheckEnabled,
|
||||||
"chat_link": common.ChatLink,
|
"turnstile_site_key": common.TurnstileSiteKey,
|
||||||
"quota_per_unit": common.QuotaPerUnit,
|
"top_up_link": common.TopUpLink,
|
||||||
"display_in_currency": common.DisplayInCurrencyEnabled,
|
"chat_link": common.ChatLink,
|
||||||
|
"quota_per_unit": common.QuotaPerUnit,
|
||||||
|
"display_in_currency": common.DisplayInCurrencyEnabled,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -109,6 +109,20 @@ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode
|
|||||||
req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
|
req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
|
||||||
req.Header.Set("Accept", c.Request.Header.Get("Accept"))
|
req.Header.Set("Accept", c.Request.Header.Get("Accept"))
|
||||||
|
|
||||||
|
custom_http_headers := c.GetString("custom_http_headers")
|
||||||
|
if custom_http_headers != "" {
|
||||||
|
var custom_http_headers_map map[string]string
|
||||||
|
err := json.Unmarshal([]byte(custom_http_headers), &custom_http_headers_map)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errorWrapper(err, "unmarshal_custom_http_headers_failed", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range custom_http_headers_map {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"one-api/model"
|
"one-api/model"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -121,8 +122,10 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
} else if channelType == common.ChannelTypeChatGPTWeb {
|
} else if channelType == common.ChannelTypeChatGPTWeb {
|
||||||
// remove /v1/chat/completions from request url
|
// remove /v1/chat/completions from request url
|
||||||
requestURL := strings.Split(requestURL, "/v1/chat/completions")[0]
|
requestURL := strings.Split(requestURL, "/v1/chat/completions")[0]
|
||||||
requestURL += "/api/chat-process"
|
fullRequestURL = fmt.Sprintf("%s%s", baseURL, requestURL)
|
||||||
|
} else if channelType == common.ChannelTypeChatbotUI {
|
||||||
|
// remove /v1/chat/completions from request url
|
||||||
|
requestURL := strings.Split(requestURL, "/v1/chat/completions")[0]
|
||||||
fullRequestURL = fmt.Sprintf("%s%s", baseURL, requestURL)
|
fullRequestURL = fmt.Sprintf("%s%s", baseURL, requestURL)
|
||||||
} else if channelType == common.ChannelTypePaLM {
|
} else if channelType == common.ChannelTypePaLM {
|
||||||
err := relayPaLM(textRequest, c)
|
err := relayPaLM(textRequest, c)
|
||||||
@@ -225,11 +228,57 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct json data without adding escape character
|
// Construct json data without adding escape character
|
||||||
map1 := map[string]string{
|
map1 := make(map[string]interface{})
|
||||||
"prompt": prompt,
|
|
||||||
"systemMessage": systemMessage.Content,
|
map1["prompt"] = prompt + "\nResponse as assistant, but do not include the role in response."
|
||||||
"temperature": strconv.FormatFloat(reqBody.Temperature, 'f', 2, 64),
|
map1["systemMessage"] = systemMessage.Content
|
||||||
"top_p": strconv.FormatFloat(reqBody.TopP, 'f', 2, 64),
|
|
||||||
|
if reqBody.Temperature != 0 {
|
||||||
|
map1["temperature"] = formatFloat(reqBody.Temperature)
|
||||||
|
}
|
||||||
|
if reqBody.TopP != 0 {
|
||||||
|
map1["top_p"] = formatFloat(reqBody.TopP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to json string
|
||||||
|
jsonData, err := json.Marshal(map1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errorWrapper(err, "marshal_json_failed", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert json string to io.Reader
|
||||||
|
requestBody = bytes.NewReader(jsonData)
|
||||||
|
} else if channelType == common.ChannelTypeChatbotUI {
|
||||||
|
// Get system message from Message json, Role == "system"
|
||||||
|
var reqBody ChatRequest
|
||||||
|
|
||||||
|
// Parse requestBody into systemMessage
|
||||||
|
err := json.NewDecoder(requestBody).Decode(&reqBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errorWrapper(err, "decode_request_body_failed", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get system message from Message json, Role == "system"
|
||||||
|
var systemMessage string
|
||||||
|
|
||||||
|
for _, message := range reqBody.Messages {
|
||||||
|
if message.Role == "system" {
|
||||||
|
systemMessage = message.Content
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct json data without adding escape character
|
||||||
|
map1 := make(map[string]interface{})
|
||||||
|
|
||||||
|
map1["prompt"] = systemMessage
|
||||||
|
map1["temperature"] = formatFloat(reqBody.Temperature)
|
||||||
|
map1["key"] = ""
|
||||||
|
map1["messages"] = reqBody.Messages
|
||||||
|
map1["model"] = map[string]interface{}{
|
||||||
|
"id": reqBody.Model,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert map to json string
|
// Convert map to json string
|
||||||
@@ -271,6 +320,20 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
req.Header.Set("X-Remote-Addr", ip)
|
req.Header.Set("X-Remote-Addr", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
custom_http_headers := c.GetString("custom_http_headers")
|
||||||
|
if custom_http_headers != "" {
|
||||||
|
var custom_http_headers_map map[string]string
|
||||||
|
err := json.Unmarshal([]byte(custom_http_headers), &custom_http_headers_map)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errorWrapper(err, "unmarshal_custom_http_headers_failed", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range custom_http_headers_map {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -281,7 +344,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
if resp.Body != nil {
|
if resp.Body != nil {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
buf.ReadFrom(resp.Body)
|
buf.ReadFrom(resp.Body)
|
||||||
log.Printf("Error Channel (%s): %s", baseURL, buf.String())
|
log.Printf("Error Channel (%s) (%s): %s", baseURL, textRequest.Model, buf.String())
|
||||||
return errorWrapper(err, "request_failed", resp.StatusCode)
|
return errorWrapper(err, "request_failed", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,20 +409,46 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if isStream {
|
if isStream || channelType == common.ChannelTypeChatGPTWeb || channelType == common.ChannelTypeChatbotUI {
|
||||||
dataChan := make(chan string)
|
dataChan := make(chan string)
|
||||||
stopChan := make(chan bool)
|
stopChan := make(chan bool)
|
||||||
|
|
||||||
if channelType == common.ChannelTypeChatGPTWeb {
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
scanner := bufio.NewScanner(resp.Body)
|
|
||||||
go func() {
|
|
||||||
for scanner.Scan() {
|
|
||||||
var chatResponse ChatGptWebChatResponse
|
|
||||||
err = json.Unmarshal(scanner.Bytes(), &chatResponse)
|
|
||||||
|
|
||||||
|
if channelType != common.ChannelTypeChatGPTWeb && channelType != common.ChannelTypeChatbotUI {
|
||||||
|
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
if atEOF && len(data) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if i := strings.Index(string(data), "\n"); i >= 0 {
|
||||||
|
return i + 2, data[0:i], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for scanner.Scan() {
|
||||||
|
data := scanner.Text()
|
||||||
|
if len(data) < 6 { // must be something wrong!
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if channelType == common.ChannelTypeChatGPTWeb {
|
||||||
|
var chatResponse ChatGptWebChatResponse
|
||||||
|
err = json.Unmarshal([]byte(data), &chatResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error unmarshal chat response: " + err.Error())
|
// Print the body in string
|
||||||
continue
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(resp.Body)
|
||||||
|
common.SysError("error unmarshalling chat response: " + err.Error() + " " + buf.String())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if response role is assistant and contains delta, append the content to streamResponseText
|
// if response role is assistant and contains delta, append the content to streamResponseText
|
||||||
@@ -389,33 +478,28 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
dataChan <- "data: " + string(jsonData)
|
dataChan <- "data: " + string(jsonData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if channelType == common.ChannelTypeChatbotUI {
|
||||||
stopChan <- true
|
returnObj := map[string]interface{}{
|
||||||
}()
|
"id": "chatcmpl-" + strconv.Itoa(int(time.Now().UnixNano())),
|
||||||
} else {
|
"object": "text_completion",
|
||||||
scanner := bufio.NewScanner(resp.Body)
|
"created": time.Now().Unix(),
|
||||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
"model": textRequest.Model,
|
||||||
if atEOF && len(data) == 0 {
|
"choices": []map[string]interface{}{
|
||||||
return 0, nil, nil
|
// set finish_reason to null in json
|
||||||
}
|
{
|
||||||
|
"finish_reason": nil,
|
||||||
if i := strings.Index(string(data), "\n"); i >= 0 {
|
"index": 0,
|
||||||
return i + 1, data[0:i], nil
|
"delta": map[string]interface{}{
|
||||||
}
|
"content": data,
|
||||||
|
},
|
||||||
if atEOF {
|
},
|
||||||
return len(data), data, nil
|
},
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil, nil
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
for scanner.Scan() {
|
|
||||||
data := scanner.Text()
|
|
||||||
if len(data) < 6 { // must be something wrong!
|
|
||||||
// common.SysError("invalid stream response: " + data)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonData, _ := json.Marshal(returnObj)
|
||||||
|
|
||||||
|
dataChan <- "data: " + string(jsonData)
|
||||||
|
} else {
|
||||||
// If data has event: event content inside, remove it, it can be prefix or inside the data
|
// If data has event: event content inside, remove it, it can be prefix or inside the data
|
||||||
if strings.HasPrefix(data, "event:") || strings.Contains(data, "event:") {
|
if strings.HasPrefix(data, "event:") || strings.Contains(data, "event:") {
|
||||||
// Remove event: event in the front or back
|
// Remove event: event in the front or back
|
||||||
@@ -465,10 +549,11 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
stopChan <- true
|
}
|
||||||
}()
|
stopChan <- true
|
||||||
}
|
}()
|
||||||
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||||
|
|||||||
9
go.mod
9
go.mod
@@ -19,11 +19,16 @@ require (
|
|||||||
gorm.io/gorm v1.25.2
|
gorm.io/gorm v1.25.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||||
|
github.com/knz/go-libedit v1.10.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
||||||
github.com/bytedance/sonic v1.9.2 // indirect
|
github.com/bytedance/sonic v1.10.0-rc2 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
|||||||
11
go.sum
11
go.sum
@@ -5,6 +5,10 @@ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s
|
|||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
||||||
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc h1:3S5HeWxjX08CUqNrXtEittExpJsEKBNzrV5UnrzHxVQ=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc2 h1:oDfRZ+4m6AYCOC0GFeOCeYqvBmucy1isvouS2K0cPzo=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
@@ -12,6 +16,10 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -99,6 +107,8 @@ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZX
|
|||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
@@ -251,4 +261,5 @@ gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
|||||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
16
i18n/en.json
16
i18n/en.json
@@ -237,7 +237,8 @@
|
|||||||
"保存首页内容": "Save Home Page Content",
|
"保存首页内容": "Save Home Page Content",
|
||||||
"在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面": "Enter new about content here, supports Markdown & HTML code. If a link is entered, it will be used as the src attribute of the iframe, allowing you to set any webpage as the about page.",
|
"在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面": "Enter new about content here, supports Markdown & HTML code. If a link is entered, it will be used as the src attribute of the iframe, allowing you to set any webpage as the about page.",
|
||||||
"保存关于": "Save About",
|
"保存关于": "Save About",
|
||||||
"移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "Removal of One API copyright mark must first be authorized. Project maintenance requires a lot of effort. If this project is meaningful to you, please actively support it.",
|
"移除 One API": "Removal of One API",
|
||||||
|
"的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。": " copyright mark must first be authorized. Project maintenance requires a lot of effort. If this project is meaningful to you, please actively support it.",
|
||||||
"页脚": "Footer",
|
"页脚": "Footer",
|
||||||
"在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码": "Enter the new footer here, leave blank to use the default footer, supports HTML code.",
|
"在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码": "Enter the new footer here, leave blank to use the default footer, supports HTML code.",
|
||||||
"设置页脚": "Set Footer",
|
"设置页脚": "Set Footer",
|
||||||
@@ -525,5 +526,16 @@
|
|||||||
"无法启用 Discord OAuth,请先填入 Discord Client ID 以及 Discord Client Secret!": "Unable to enable Discord OAuth, please fill in the Discord Client ID and Discord Client Secret first!",
|
"无法启用 Discord OAuth,请先填入 Discord Client ID 以及 Discord Client Secret!": "Unable to enable Discord OAuth, please fill in the Discord Client ID and Discord Client Secret first!",
|
||||||
"兑换失败,": "Redemption failed, ",
|
"兑换失败,": "Redemption failed, ",
|
||||||
"请选择此密钥支持的模型": "Please select the models supported by this key",
|
"请选择此密钥支持的模型": "Please select the models supported by this key",
|
||||||
"将IP随机地址传递给HTTP头": "Pass the IP random address to the HTTP header"
|
"将IP随机地址传递给HTTP头": "Pass the IP random address to the HTTP header",
|
||||||
|
"失败重试次数": "Number of failed retries",
|
||||||
|
"消费": "Consumption",
|
||||||
|
"管理": "Management",
|
||||||
|
"系统": "System",
|
||||||
|
"未知": "Unknown",
|
||||||
|
"One API 会把请求体中的 model": "One API will take the model in the request body",
|
||||||
|
",因为": ", because",
|
||||||
|
"参数替换为你的部署名称(模型名称中的点会被剔除),": "Replace the parameter with your deployment name (dots in the model name will be removed), ",
|
||||||
|
"注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI": "Note that the generated token here is used for system management, not for requesting OpenAI",
|
||||||
|
"相关的服务,请知悉。": "related services, please be aware.",
|
||||||
|
"填入": "Fill in"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ func Distribute() func(c *gin.Context) {
|
|||||||
c.Set("channel_id", channel.Id)
|
c.Set("channel_id", channel.Id)
|
||||||
c.Set("channel_name", channel.Name)
|
c.Set("channel_name", channel.Name)
|
||||||
c.Set("model_mapping", channel.ModelMapping)
|
c.Set("model_mapping", channel.ModelMapping)
|
||||||
|
c.Set("custom_http_headers", channel.CustomHttpHeaders)
|
||||||
c.Set("enable_ip_randomization", channel.EnableIpRandomization)
|
c.Set("enable_ip_randomization", channel.EnableIpRandomization)
|
||||||
c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", channel.Key))
|
c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", channel.Key))
|
||||||
c.Set("base_url", channel.BaseURL)
|
c.Set("base_url", channel.BaseURL)
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ type Channel struct {
|
|||||||
ModelMapping string `json:"model_mapping" gorm:"type:varchar(1024);default:''"`
|
ModelMapping string `json:"model_mapping" gorm:"type:varchar(1024);default:''"`
|
||||||
|
|
||||||
// Additional fields, default value is false
|
// Additional fields, default value is false
|
||||||
EnableIpRandomization bool `json:"enable_ip_randomization"`
|
EnableIpRandomization bool `json:"enable_ip_randomization"`
|
||||||
|
CustomHttpHeaders string `json:"custom_http_headers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
|
func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ func InitOptionMap() {
|
|||||||
common.OptionMap["GitHubClientSecret"] = ""
|
common.OptionMap["GitHubClientSecret"] = ""
|
||||||
common.OptionMap["DiscordClientId"] = ""
|
common.OptionMap["DiscordClientId"] = ""
|
||||||
common.OptionMap["DiscordClientSecret"] = ""
|
common.OptionMap["DiscordClientSecret"] = ""
|
||||||
|
common.OptionMap["DiscordGuildId"] = ""
|
||||||
|
common.OptionMap["DiscordBotToken"] = ""
|
||||||
|
common.OptionMap["DiscordAllowJoiningGuild"] = ""
|
||||||
common.OptionMap["WeChatServerAddress"] = ""
|
common.OptionMap["WeChatServerAddress"] = ""
|
||||||
common.OptionMap["WeChatServerToken"] = ""
|
common.OptionMap["WeChatServerToken"] = ""
|
||||||
common.OptionMap["WeChatAccountQRCodeImageURL"] = ""
|
common.OptionMap["WeChatAccountQRCodeImageURL"] = ""
|
||||||
@@ -178,6 +181,12 @@ func updateOptionMap(key string, value string) (err error) {
|
|||||||
common.GitHubClientSecret = value
|
common.GitHubClientSecret = value
|
||||||
case "DiscordClientId":
|
case "DiscordClientId":
|
||||||
common.DiscordClientId = value
|
common.DiscordClientId = value
|
||||||
|
case "DiscordGuildId":
|
||||||
|
common.DiscordGuildId = value
|
||||||
|
case "DiscordBotToken":
|
||||||
|
common.DiscordBotToken = value
|
||||||
|
case "DiscordAllowJoiningGuild":
|
||||||
|
common.DiscordAllowJoiningGuild = value
|
||||||
case "DiscordClientSecret":
|
case "DiscordClientSecret":
|
||||||
common.DiscordClientSecret = value
|
common.DiscordClientSecret = value
|
||||||
case "Footer":
|
case "Footer":
|
||||||
|
|||||||
19
web/package-lock.json
generated
19
web/package-lock.json
generated
@@ -23,7 +23,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
"prettier": "3.0.0",
|
"prettier": "3.0.0",
|
||||||
"terser": "^5.19.0",
|
|
||||||
"vite": "^4.4.4"
|
"vite": "^4.4.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -794,6 +793,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
|
||||||
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
|
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.0",
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
"@jridgewell/trace-mapping": "^0.3.9"
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
@@ -900,6 +901,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||||
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
|
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -978,7 +981,9 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001513",
|
"version": "1.0.30001513",
|
||||||
@@ -1616,6 +1621,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
@@ -1626,6 +1633,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -1647,6 +1656,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.0.tgz",
|
||||||
"integrity": "sha512-JpcpGOQLOXm2jsomozdMDpd5f8ZHh1rR48OFgWUH3QsyZcfPgv2qDCYbcDEAYNd4OZRj2bWYKpwdll/udZCk/Q==",
|
"integrity": "sha512-JpcpGOQLOXm2jsomozdMDpd5f8ZHh1rR48OFgWUH3QsyZcfPgv2qDCYbcDEAYNd4OZRj2bWYKpwdll/udZCk/Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.8.2",
|
"acorn": "^8.8.2",
|
||||||
@@ -1664,7 +1675,9 @@
|
|||||||
"version": "2.20.3",
|
"version": "2.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/to-fast-properties": {
|
"node_modules/to-fast-properties": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
|||||||
@@ -40,7 +40,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
"prettier": "3.0.0",
|
"prettier": "3.0.0",
|
||||||
"terser": "^5.19.0",
|
|
||||||
"vite": "^4.4.4"
|
"vite": "^4.4.4"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const LoginForm = () => {
|
|||||||
|
|
||||||
const onDiscordOAuthClicked = () => {
|
const onDiscordOAuthClicked = () => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://discord.com/oauth2/authorize?response_type=code&client_id=${status.discord_client_id}&redirect_uri=${window.location.origin}/oauth/discord&scope=identify`,
|
`https://discord.com/oauth2/authorize?response_type=code&client_id=${status.discord_client_id}&redirect_uri=${window.location.origin}/oauth/discord&scope=identify%20guilds%20email%20guilds.join`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ const PersonalSetting = () => {
|
|||||||
|
|
||||||
const openDiscordOAuth = () => {
|
const openDiscordOAuth = () => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://discord.com/api/oauth2/authorize?client_id=${status.discord_client_id}&scope=identify%20email&response_type=code&redirect_uri=${window.location.origin}/oauth/discord`,
|
`https://discord.com/api/oauth2/authorize?client_id=${status.discord_client_id}&response_type=code&redirect_uri=${window.location.origin}/oauth/discord&scope=identify%20guilds%20email%20guilds.join`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ const SystemSetting = () => {
|
|||||||
GitHubClientId: '',
|
GitHubClientId: '',
|
||||||
GitHubClientSecret: '',
|
GitHubClientSecret: '',
|
||||||
DiscordClientId: '',
|
DiscordClientId: '',
|
||||||
|
DiscordAllowJoiningGuild: 'false',
|
||||||
|
DiscordGuildId: '',
|
||||||
|
DiscordBotToken: '',
|
||||||
DiscordClientSecret: '',
|
DiscordClientSecret: '',
|
||||||
Notice: '',
|
Notice: '',
|
||||||
SMTPServer: '',
|
SMTPServer: '',
|
||||||
@@ -87,6 +90,9 @@ const SystemSetting = () => {
|
|||||||
name.startsWith('SMTP') ||
|
name.startsWith('SMTP') ||
|
||||||
name === 'ServerAddress' ||
|
name === 'ServerAddress' ||
|
||||||
name === 'DiscordClientId' ||
|
name === 'DiscordClientId' ||
|
||||||
|
name === 'DiscordGuildId' ||
|
||||||
|
name === 'DiscordAllowJoiningGuild' ||
|
||||||
|
name === 'DiscordBotToken' ||
|
||||||
name === 'DiscordClientSecret' ||
|
name === 'DiscordClientSecret' ||
|
||||||
name === 'GitHubClientId' ||
|
name === 'GitHubClientId' ||
|
||||||
name === 'GitHubClientSecret' ||
|
name === 'GitHubClientSecret' ||
|
||||||
@@ -177,6 +183,24 @@ const SystemSetting = () => {
|
|||||||
) {
|
) {
|
||||||
await updateOption('DiscordClientSecret', inputs.DiscordClientSecret);
|
await updateOption('DiscordClientSecret', inputs.DiscordClientSecret);
|
||||||
}
|
}
|
||||||
|
if (originInputs['DiscordGuildId'] !== inputs.DiscordGuildId) {
|
||||||
|
await updateOption('DiscordGuildId', inputs.DiscordGuildId);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
originInputs['DiscordBotToken'] !== inputs.DiscordBotToken &&
|
||||||
|
inputs.DiscordBotToken !== ''
|
||||||
|
) {
|
||||||
|
await updateOption('DiscordBotToken', inputs.DiscordBotToken);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
originInputs['DiscordAllowJoiningGuild'] !==
|
||||||
|
inputs.DiscordAllowJoiningGuild
|
||||||
|
) {
|
||||||
|
await updateOption(
|
||||||
|
'DiscordAllowJoiningGuild',
|
||||||
|
inputs.DiscordAllowJoiningGuild,
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitTurnstile = async () => {
|
const submitTurnstile = async () => {
|
||||||
@@ -352,6 +376,32 @@ const SystemSetting = () => {
|
|||||||
value={inputs.DiscordClientSecret}
|
value={inputs.DiscordClientSecret}
|
||||||
placeholder='Sensitive information will not be displayed in the frontend'
|
placeholder='Sensitive information will not be displayed in the frontend'
|
||||||
/>
|
/>
|
||||||
|
<Form.Checkbox
|
||||||
|
label='Allow Joining Guild'
|
||||||
|
name='DiscordAllowJoiningGuild'
|
||||||
|
autoComplete='new-password'
|
||||||
|
checked={inputs.DiscordAllowJoiningGuild === 'true'}
|
||||||
|
onChange={(e, { name, checked }) =>
|
||||||
|
handleInputChange(e, { name, value: checked ? 'true' : 'false' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
label='Discord Guild ID'
|
||||||
|
name='DiscordGuildId'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.DiscordGuildId}
|
||||||
|
placeholder='Enter the ID of your Discord server'
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
label='Discord Bot Token'
|
||||||
|
name='DiscordBotToken'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
type='password'
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.DiscordBotToken}
|
||||||
|
placeholder='Sensitive information will not be displayed in the frontend'
|
||||||
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitDiscordOAuth}>
|
<Form.Button onClick={submitDiscordOAuth}>
|
||||||
Save Discord OAuth Settings
|
Save Discord OAuth Settings
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ export const CHANNEL_OPTIONS = [
|
|||||||
|
|
||||||
//
|
//
|
||||||
{ key: 14, text: 'Chanzhaoyu/chatgpt-web', value: 14, color: 'purple' },
|
{ key: 14, text: 'Chanzhaoyu/chatgpt-web', value: 14, color: 'purple' },
|
||||||
|
{ key: 14, text: 'mckaywrigley/chatbot-ui', value: 15, color: 'orange' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ const MODEL_MAPPING_EXAMPLE = {
|
|||||||
'gpt-4-32k-0314': 'gpt-4-32k',
|
'gpt-4-32k-0314': 'gpt-4-32k',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CUSTOM_HTTP_HEADERS_EXAMPLE = {
|
||||||
|
'X-OpenAI-Organization': 'OpenAI',
|
||||||
|
};
|
||||||
|
|
||||||
const EditChannel = () => {
|
const EditChannel = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const channelId = params.id;
|
const channelId = params.id;
|
||||||
@@ -35,6 +39,7 @@ const EditChannel = () => {
|
|||||||
base_url: '',
|
base_url: '',
|
||||||
other: '',
|
other: '',
|
||||||
model_mapping: '',
|
model_mapping: '',
|
||||||
|
custom_http_headers: '',
|
||||||
models: [],
|
models: [],
|
||||||
groups: ['default'],
|
groups: ['default'],
|
||||||
enable_ip_randomization: false,
|
enable_ip_randomization: false,
|
||||||
@@ -51,7 +56,7 @@ const EditChannel = () => {
|
|||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadChannel = async () => {
|
const loadChannel = async (modelOptions) => {
|
||||||
let res = await API.get(`/api/channel/${channelId}`);
|
let res = await API.get(`/api/channel/${channelId}`);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -85,6 +90,13 @@ const EditChannel = () => {
|
|||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.custom_http_headers !== '') {
|
||||||
|
data.custom_http_headers = JSON.stringify(
|
||||||
|
JSON.parse(data.custom_http_headers),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
setInputs(data);
|
setInputs(data);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
@@ -95,19 +107,23 @@ const EditChannel = () => {
|
|||||||
const fetchModels = async () => {
|
const fetchModels = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await API.get(`/api/channel/models`);
|
let res = await API.get(`/api/channel/models`);
|
||||||
setModelOptions(
|
|
||||||
res.data.data.map((model) => ({
|
|
||||||
key: model.id,
|
|
||||||
text: model.id,
|
|
||||||
value: model.id,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
setFullModels(res.data.data.map((model) => model.id));
|
setFullModels(res.data.data.map((model) => model.id));
|
||||||
setBasicModels(
|
setBasicModels(
|
||||||
res.data.data
|
res.data.data
|
||||||
.filter((model) => !model.id.startsWith('gpt-4'))
|
.filter((model) => !model.id.startsWith('gpt-4'))
|
||||||
.map((model) => model.id),
|
.map((model) => model.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const allModels = res.data.data.map((model) => ({
|
||||||
|
key: model.id,
|
||||||
|
text: model.id,
|
||||||
|
value: model.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setModelOptions(allModels);
|
||||||
|
|
||||||
|
return allModels;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
@@ -128,12 +144,12 @@ const EditChannel = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(async () => {
|
||||||
|
const models = await fetchModels();
|
||||||
|
await fetchGroups();
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
loadChannel().then();
|
await loadChannel(models);
|
||||||
}
|
}
|
||||||
fetchModels().then();
|
|
||||||
fetchGroups().then();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
@@ -149,6 +165,13 @@ const EditChannel = () => {
|
|||||||
showInfo('模型映射必须是合法的 JSON 格式!');
|
showInfo('模型映射必须是合法的 JSON 格式!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
inputs.custom_http_headers !== '' &&
|
||||||
|
!verifyJSON(inputs.custom_http_headers)
|
||||||
|
) {
|
||||||
|
showInfo('自定义 HTTP 头必须是合法的 JSON 格式!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
let localInputs = inputs;
|
let localInputs = inputs;
|
||||||
if (localInputs.base_url.endsWith('/')) {
|
if (localInputs.base_url.endsWith('/')) {
|
||||||
localInputs.base_url = localInputs.base_url.slice(
|
localInputs.base_url = localInputs.base_url.slice(
|
||||||
@@ -390,6 +413,21 @@ const EditChannel = () => {
|
|||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<Form.TextArea
|
||||||
|
label='自定义 HTTP 头'
|
||||||
|
placeholder={`此项可选,为一个 JSON 文本,键为 HTTP 头名称,值为 HTTP 头内容,例如:\n${JSON.stringify(
|
||||||
|
CUSTOM_HTTP_HEADERS_EXAMPLE,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}`}
|
||||||
|
name='custom_http_headers'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={inputs.custom_http_headers}
|
||||||
|
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
{batch ? (
|
{batch ? (
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
|
|||||||
@@ -6,6 +6,5 @@ export default defineConfig({
|
|||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
build: {
|
build: {
|
||||||
outDir: 'build',
|
outDir: 'build',
|
||||||
minify: 'terser',
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user