BG5 commited on
Commit
c6be9e8
·
1 Parent(s): a9febac

Upload 134 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:16 as builder
2
+
3
+ WORKDIR /build
4
+ COPY web/package.json .
5
+ RUN npm install
6
+ COPY ./web .
7
+ COPY ./VERSION .
8
+ RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build
9
+
10
+ FROM golang AS builder2
11
+
12
+ ENV GO111MODULE=on \
13
+ CGO_ENABLED=1 \
14
+ GOOS=linux
15
+
16
+ WORKDIR /build
17
+ ADD go.mod go.sum ./
18
+ RUN go mod download
19
+ COPY . .
20
+ COPY --from=builder /build/build ./web/build
21
+ RUN go build -ldflags "-s -w -X 'one-api/common.Version=$(cat VERSION)' -extldflags '-static'" -o one-api
22
+
23
+ FROM alpine
24
+
25
+ RUN apk update \
26
+ && apk upgrade \
27
+ && apk add --no-cache ca-certificates tzdata \
28
+ && update-ca-certificates 2>/dev/null || true
29
+
30
+ COPY --from=builder2 /build/one-api /
31
+ EXPOSE 3000
32
+ WORKDIR /data
33
+ ENTRYPOINT ["/one-api"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 JustSong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
VERSION ADDED
File without changes
bin/migration_v0.2-v0.3.sql ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ UPDATE users
2
+ SET quota = quota + (
3
+ SELECT SUM(remain_quota)
4
+ FROM tokens
5
+ WHERE tokens.user_id = users.id
6
+ )
bin/migration_v0.3-v0.4.sql ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ INSERT INTO abilities (`group`, model, channel_id, enabled)
2
+ SELECT c.`group`, m.model, c.id, 1
3
+ FROM channels c
4
+ CROSS JOIN (
5
+ SELECT 'gpt-3.5-turbo' AS model UNION ALL
6
+ SELECT 'gpt-3.5-turbo-0301' AS model UNION ALL
7
+ SELECT 'gpt-4' AS model UNION ALL
8
+ SELECT 'gpt-4-0314' AS model
9
+ ) AS m
10
+ WHERE c.status = 1
11
+ AND NOT EXISTS (
12
+ SELECT 1
13
+ FROM abilities a
14
+ WHERE a.`group` = c.`group`
15
+ AND a.model = m.model
16
+ AND a.channel_id = c.id
17
+ );
bin/time_test.sh ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ if [ $# -lt 3 ]; then
4
+ echo "Usage: time_test.sh <domain> <key> <count> [<model>]"
5
+ exit 1
6
+ fi
7
+
8
+ domain=$1
9
+ key=$2
10
+ count=$3
11
+ model=${4:-"gpt-3.5-turbo"} # 设置默认模型为 gpt-3.5-turbo
12
+
13
+ total_time=0
14
+ times=()
15
+
16
+ for ((i=1; i<=count; i++)); do
17
+ result=$(curl -o /dev/null -s -w "%{http_code} %{time_total}\\n" \
18
+ https://"$domain"/v1/chat/completions \
19
+ -H "Content-Type: application/json" \
20
+ -H "Authorization: Bearer $key" \
21
+ -d '{"messages": [{"content": "echo hi", "role": "user"}], "model": "'"$model"'", "stream": false, "max_tokens": 1}')
22
+ http_code=$(echo "$result" | awk '{print $1}')
23
+ time=$(echo "$result" | awk '{print $2}')
24
+ echo "HTTP status code: $http_code, Time taken: $time"
25
+ total_time=$(bc <<< "$total_time + $time")
26
+ times+=("$time")
27
+ done
28
+
29
+ average_time=$(echo "scale=4; $total_time / $count" | bc)
30
+
31
+ sum_of_squares=0
32
+ for time in "${times[@]}"; do
33
+ difference=$(echo "scale=4; $time - $average_time" | bc)
34
+ square=$(echo "scale=4; $difference * $difference" | bc)
35
+ sum_of_squares=$(echo "scale=4; $sum_of_squares + $square" | bc)
36
+ done
37
+
38
+ standard_deviation=$(echo "scale=4; sqrt($sum_of_squares / $count)" | bc)
39
+
40
+ echo "Average time: $average_time±$standard_deviation"
common/constants.go ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "os"
5
+ "strconv"
6
+ "sync"
7
+ "time"
8
+
9
+ "github.com/google/uuid"
10
+ )
11
+
12
+ var StartTime = time.Now().Unix() // unit: second
13
+ var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change
14
+ var SystemName = "One API"
15
+ var ServerAddress = "http://localhost:3000"
16
+ var Footer = ""
17
+ var Logo = ""
18
+ var TopUpLink = ""
19
+ var ChatLink = ""
20
+ var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
21
+ var DisplayInCurrencyEnabled = true
22
+ var DisplayTokenStatEnabled = true
23
+
24
+ var UsingSQLite = false
25
+
26
+ // Any options with "Secret", "Token" in its key won't be return by GetOptions
27
+
28
+ var SessionSecret = uuid.New().String()
29
+ var SQLitePath = "one-api.db"
30
+
31
+ var OptionMap map[string]string
32
+ var OptionMapRWMutex sync.RWMutex
33
+
34
+ var ItemsPerPage = 10
35
+ var MaxRecentItems = 100
36
+
37
+ var PasswordLoginEnabled = true
38
+ var PasswordRegisterEnabled = true
39
+ var EmailVerificationEnabled = false
40
+ var GitHubOAuthEnabled = false
41
+ var WeChatAuthEnabled = false
42
+ var TurnstileCheckEnabled = false
43
+ var RegisterEnabled = true
44
+
45
+ var EmailDomainRestrictionEnabled = false
46
+ var EmailDomainWhitelist = []string{
47
+ "gmail.com",
48
+ "163.com",
49
+ "126.com",
50
+ "qq.com",
51
+ "outlook.com",
52
+ "hotmail.com",
53
+ "icloud.com",
54
+ "yahoo.com",
55
+ "foxmail.com",
56
+ }
57
+
58
+ var DebugEnabled = os.Getenv("DEBUG") == "true"
59
+
60
+ var LogConsumeEnabled = true
61
+
62
+ var SMTPServer = ""
63
+ var SMTPPort = 587
64
+ var SMTPAccount = ""
65
+ var SMTPFrom = ""
66
+ var SMTPToken = ""
67
+
68
+ var GitHubClientId = ""
69
+ var GitHubClientSecret = ""
70
+
71
+ var WeChatServerAddress = ""
72
+ var WeChatServerToken = ""
73
+ var WeChatAccountQRCodeImageURL = ""
74
+
75
+ var TurnstileSiteKey = ""
76
+ var TurnstileSecretKey = ""
77
+
78
+ var QuotaForNewUser = 0
79
+ var QuotaForInviter = 0
80
+ var QuotaForInvitee = 0
81
+ var ChannelDisableThreshold = 5.0
82
+ var AutomaticDisableChannelEnabled = false
83
+ var QuotaRemindThreshold = 1000
84
+ var PreConsumedQuota = 500
85
+ var ApproximateTokenEnabled = false
86
+ var RetryTimes = 0
87
+
88
+ var RootUserEmail = ""
89
+
90
+ var IsMasterNode = os.Getenv("NODE_TYPE") != "slave"
91
+
92
+ var requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
93
+ var RequestInterval = time.Duration(requestInterval) * time.Second
94
+
95
+ var SyncFrequency = 10 * 60 // unit is second, will be overwritten by SYNC_FREQUENCY
96
+
97
+ const (
98
+ RoleGuestUser = 0
99
+ RoleCommonUser = 1
100
+ RoleAdminUser = 10
101
+ RoleRootUser = 100
102
+ )
103
+
104
+ var (
105
+ FileUploadPermission = RoleGuestUser
106
+ FileDownloadPermission = RoleGuestUser
107
+ ImageUploadPermission = RoleGuestUser
108
+ ImageDownloadPermission = RoleGuestUser
109
+ )
110
+
111
+ // All duration's unit is seconds
112
+ // Shouldn't larger then RateLimitKeyExpirationDuration
113
+ var (
114
+ GlobalApiRateLimitNum = 180
115
+ GlobalApiRateLimitDuration int64 = 3 * 60
116
+
117
+ GlobalWebRateLimitNum = 60
118
+ GlobalWebRateLimitDuration int64 = 3 * 60
119
+
120
+ UploadRateLimitNum = 10
121
+ UploadRateLimitDuration int64 = 60
122
+
123
+ DownloadRateLimitNum = 10
124
+ DownloadRateLimitDuration int64 = 60
125
+
126
+ CriticalRateLimitNum = 20
127
+ CriticalRateLimitDuration int64 = 20 * 60
128
+ )
129
+
130
+ var RateLimitKeyExpirationDuration = 20 * time.Minute
131
+
132
+ const (
133
+ UserStatusEnabled = 1 // don't use 0, 0 is the default value!
134
+ UserStatusDisabled = 2 // also don't use 0
135
+ )
136
+
137
+ const (
138
+ TokenStatusEnabled = 1 // don't use 0, 0 is the default value!
139
+ TokenStatusDisabled = 2 // also don't use 0
140
+ TokenStatusExpired = 3
141
+ TokenStatusExhausted = 4
142
+ )
143
+
144
+ const (
145
+ RedemptionCodeStatusEnabled = 1 // don't use 0, 0 is the default value!
146
+ RedemptionCodeStatusDisabled = 2 // also don't use 0
147
+ RedemptionCodeStatusUsed = 3 // also don't use 0
148
+ )
149
+
150
+ const (
151
+ ChannelStatusUnknown = 0
152
+ ChannelStatusEnabled = 1 // don't use 0, 0 is the default value!
153
+ ChannelStatusDisabled = 2 // also don't use 0
154
+ )
155
+
156
+ const (
157
+ ChannelTypeUnknown = 0
158
+ ChannelTypeOpenAI = 1
159
+ ChannelTypeAPI2D = 2
160
+ ChannelTypeAzure = 3
161
+ ChannelTypeCloseAI = 4
162
+ ChannelTypeOpenAISB = 5
163
+ ChannelTypeOpenAIMax = 6
164
+ ChannelTypeOhMyGPT = 7
165
+ ChannelTypeCustom = 8
166
+ ChannelTypeAILS = 9
167
+ ChannelTypeAIProxy = 10
168
+ ChannelTypePaLM = 11
169
+ ChannelTypeAPI2GPT = 12
170
+ ChannelTypeAIGC2D = 13
171
+ ChannelTypeAnthropic = 14
172
+ ChannelTypeBaidu = 15
173
+ ChannelTypeZhipu = 16
174
+ ChannelTypeAli = 17
175
+ ChannelTypeXunfei = 18
176
+ ChannelType360 = 19
177
+ ChannelTypeOpenRouter = 20
178
+ )
179
+
180
+ var ChannelBaseURLs = []string{
181
+ "", // 0
182
+ "https://api.openai.com", // 1
183
+ "https://oa.api2d.net", // 2
184
+ "", // 3
185
+ "https://api.closeai-proxy.xyz", // 4
186
+ "https://api.openai-sb.com", // 5
187
+ "https://api.openaimax.com", // 6
188
+ "https://api.ohmygpt.com", // 7
189
+ "", // 8
190
+ "https://api.caipacity.com", // 9
191
+ "https://api.aiproxy.io", // 10
192
+ "", // 11
193
+ "https://api.api2gpt.com", // 12
194
+ "https://api.aigc2d.com", // 13
195
+ "https://api.anthropic.com", // 14
196
+ "https://aip.baidubce.com", // 15
197
+ "https://open.bigmodel.cn", // 16
198
+ "https://dashscope.aliyuncs.com", // 17
199
+ "", // 18
200
+ "https://ai.360.cn", // 19
201
+ "https://openrouter.ai/api", // 20
202
+ }
common/crypto.go ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "golang.org/x/crypto/bcrypt"
4
+
5
+ func Password2Hash(password string) (string, error) {
6
+ passwordBytes := []byte(password)
7
+ hashedPassword, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
8
+ return string(hashedPassword), err
9
+ }
10
+
11
+ func ValidatePasswordAndHash(password string, hash string) bool {
12
+ err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
13
+ return err == nil
14
+ }
common/custom-event.go ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Copyright 2014 Manu Martinez-Almeida. All rights reserved.
2
+ // Use of this source code is governed by a MIT style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ package common
6
+
7
+ import (
8
+ "fmt"
9
+ "io"
10
+ "net/http"
11
+ "strings"
12
+ )
13
+
14
+ type stringWriter interface {
15
+ io.Writer
16
+ writeString(string) (int, error)
17
+ }
18
+
19
+ type stringWrapper struct {
20
+ io.Writer
21
+ }
22
+
23
+ func (w stringWrapper) writeString(str string) (int, error) {
24
+ return w.Writer.Write([]byte(str))
25
+ }
26
+
27
+ func checkWriter(writer io.Writer) stringWriter {
28
+ if w, ok := writer.(stringWriter); ok {
29
+ return w
30
+ } else {
31
+ return stringWrapper{writer}
32
+ }
33
+ }
34
+
35
+ // Server-Sent Events
36
+ // W3C Working Draft 29 October 2009
37
+ // http://www.w3.org/TR/2009/WD-eventsource-20091029/
38
+
39
+ var contentType = []string{"text/event-stream"}
40
+ var noCache = []string{"no-cache"}
41
+
42
+ var fieldReplacer = strings.NewReplacer(
43
+ "\n", "\\n",
44
+ "\r", "\\r")
45
+
46
+ var dataReplacer = strings.NewReplacer(
47
+ "\n", "\ndata:",
48
+ "\r", "\\r")
49
+
50
+ type CustomEvent struct {
51
+ Event string
52
+ Id string
53
+ Retry uint
54
+ Data interface{}
55
+ }
56
+
57
+ func encode(writer io.Writer, event CustomEvent) error {
58
+ w := checkWriter(writer)
59
+ return writeData(w, event.Data)
60
+ }
61
+
62
+ func writeData(w stringWriter, data interface{}) error {
63
+ dataReplacer.WriteString(w, fmt.Sprint(data))
64
+ if strings.HasPrefix(data.(string), "data") {
65
+ w.writeString("\n\n")
66
+ }
67
+ return nil
68
+ }
69
+
70
+ func (r CustomEvent) Render(w http.ResponseWriter) error {
71
+ r.WriteContentType(w)
72
+ return encode(w, r)
73
+ }
74
+
75
+ func (r CustomEvent) WriteContentType(w http.ResponseWriter) {
76
+ header := w.Header()
77
+ header["Content-Type"] = contentType
78
+
79
+ if _, exist := header["Cache-Control"]; !exist {
80
+ header["Cache-Control"] = noCache
81
+ }
82
+ }
common/email.go ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "crypto/tls"
5
+ "encoding/base64"
6
+ "fmt"
7
+ "net/smtp"
8
+ "strings"
9
+ )
10
+
11
+ func SendEmail(subject string, receiver string, content string) error {
12
+ if SMTPFrom == "" { // for compatibility
13
+ SMTPFrom = SMTPAccount
14
+ }
15
+ encodedSubject := fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(subject)))
16
+ mail := []byte(fmt.Sprintf("To: %s\r\n"+
17
+ "From: %s<%s>\r\n"+
18
+ "Subject: %s\r\n"+
19
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n",
20
+ receiver, SystemName, SMTPFrom, encodedSubject, content))
21
+ auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer)
22
+ addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort)
23
+ to := strings.Split(receiver, ";")
24
+ var err error
25
+ if SMTPPort == 465 {
26
+ tlsConfig := &tls.Config{
27
+ InsecureSkipVerify: true,
28
+ ServerName: SMTPServer,
29
+ }
30
+ conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", SMTPServer, SMTPPort), tlsConfig)
31
+ if err != nil {
32
+ return err
33
+ }
34
+ client, err := smtp.NewClient(conn, SMTPServer)
35
+ if err != nil {
36
+ return err
37
+ }
38
+ defer client.Close()
39
+ if err = client.Auth(auth); err != nil {
40
+ return err
41
+ }
42
+ if err = client.Mail(SMTPFrom); err != nil {
43
+ return err
44
+ }
45
+ receiverEmails := strings.Split(receiver, ";")
46
+ for _, receiver := range receiverEmails {
47
+ if err = client.Rcpt(receiver); err != nil {
48
+ return err
49
+ }
50
+ }
51
+ w, err := client.Data()
52
+ if err != nil {
53
+ return err
54
+ }
55
+ _, err = w.Write(mail)
56
+ if err != nil {
57
+ return err
58
+ }
59
+ err = w.Close()
60
+ if err != nil {
61
+ return err
62
+ }
63
+ } else {
64
+ err = smtp.SendMail(addr, auth, SMTPAccount, to, mail)
65
+ }
66
+ return err
67
+ }
common/embed-file-system.go ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "embed"
5
+ "github.com/gin-contrib/static"
6
+ "io/fs"
7
+ "net/http"
8
+ )
9
+
10
+ // Credit: https://github.com/gin-contrib/static/issues/19
11
+
12
+ type embedFileSystem struct {
13
+ http.FileSystem
14
+ }
15
+
16
+ func (e embedFileSystem) Exists(prefix string, path string) bool {
17
+ _, err := e.Open(path)
18
+ if err != nil {
19
+ return false
20
+ }
21
+ return true
22
+ }
23
+
24
+ func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
25
+ efs, err := fs.Sub(fsEmbed, targetPath)
26
+ if err != nil {
27
+ panic(err)
28
+ }
29
+ return embedFileSystem{
30
+ FileSystem: http.FS(efs),
31
+ }
32
+ }
common/gin.go ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "github.com/gin-gonic/gin"
7
+ "io"
8
+ )
9
+
10
+ func UnmarshalBodyReusable(c *gin.Context, v any) error {
11
+ requestBody, err := io.ReadAll(c.Request.Body)
12
+ if err != nil {
13
+ return err
14
+ }
15
+ err = c.Request.Body.Close()
16
+ if err != nil {
17
+ return err
18
+ }
19
+ err = json.Unmarshal(requestBody, &v)
20
+ if err != nil {
21
+ return err
22
+ }
23
+ // Reset request body
24
+ c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
25
+ return nil
26
+ }
common/group-ratio.go ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "encoding/json"
4
+
5
+ var GroupRatio = map[string]float64{
6
+ "default": 1,
7
+ "vip": 1,
8
+ "svip": 1,
9
+ }
10
+
11
+ func GroupRatio2JSONString() string {
12
+ jsonBytes, err := json.Marshal(GroupRatio)
13
+ if err != nil {
14
+ SysError("error marshalling model ratio: " + err.Error())
15
+ }
16
+ return string(jsonBytes)
17
+ }
18
+
19
+ func UpdateGroupRatioByJSONString(jsonStr string) error {
20
+ GroupRatio = make(map[string]float64)
21
+ return json.Unmarshal([]byte(jsonStr), &GroupRatio)
22
+ }
23
+
24
+ func GetGroupRatio(name string) float64 {
25
+ ratio, ok := GroupRatio[name]
26
+ if !ok {
27
+ SysError("group ratio not found: " + name)
28
+ return 1
29
+ }
30
+ return ratio
31
+ }
common/init.go ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "flag"
5
+ "fmt"
6
+ "log"
7
+ "os"
8
+ "path/filepath"
9
+ )
10
+
11
+ var (
12
+ Port = flag.Int("port", 3000, "the listening port")
13
+ PrintVersion = flag.Bool("version", false, "print version and exit")
14
+ PrintHelp = flag.Bool("help", false, "print help and exit")
15
+ LogDir = flag.String("log-dir", "", "specify the log directory")
16
+ )
17
+
18
+ func printHelp() {
19
+ fmt.Println("One API " + Version + " - All in one API service for OpenAI API.")
20
+ fmt.Println("Copyright (C) 2023 JustSong. All rights reserved.")
21
+ fmt.Println("GitHub: https://github.com/songquanpeng/one-api")
22
+ fmt.Println("Usage: one-api [--port <port>] [--log-dir <log directory>] [--version] [--help]")
23
+ }
24
+
25
+ func init() {
26
+ flag.Parse()
27
+
28
+ if *PrintVersion {
29
+ fmt.Println(Version)
30
+ os.Exit(0)
31
+ }
32
+
33
+ if *PrintHelp {
34
+ printHelp()
35
+ os.Exit(0)
36
+ }
37
+
38
+ if os.Getenv("SESSION_SECRET") != "" {
39
+ SessionSecret = os.Getenv("SESSION_SECRET")
40
+ }
41
+ if os.Getenv("SQLITE_PATH") != "" {
42
+ SQLitePath = os.Getenv("SQLITE_PATH")
43
+ }
44
+ if *LogDir != "" {
45
+ var err error
46
+ *LogDir, err = filepath.Abs(*LogDir)
47
+ if err != nil {
48
+ log.Fatal(err)
49
+ }
50
+ if _, err := os.Stat(*LogDir); os.IsNotExist(err) {
51
+ err = os.Mkdir(*LogDir, 0777)
52
+ if err != nil {
53
+ log.Fatal(err)
54
+ }
55
+ }
56
+ }
57
+ }
common/logger.go ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "fmt"
5
+ "github.com/gin-gonic/gin"
6
+ "io"
7
+ "log"
8
+ "os"
9
+ "path/filepath"
10
+ "time"
11
+ )
12
+
13
+ func SetupGinLog() {
14
+ if *LogDir != "" {
15
+ commonLogPath := filepath.Join(*LogDir, "common.log")
16
+ errorLogPath := filepath.Join(*LogDir, "error.log")
17
+ commonFd, err := os.OpenFile(commonLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
18
+ if err != nil {
19
+ log.Fatal("failed to open log file")
20
+ }
21
+ errorFd, err := os.OpenFile(errorLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
22
+ if err != nil {
23
+ log.Fatal("failed to open log file")
24
+ }
25
+ gin.DefaultWriter = io.MultiWriter(os.Stdout, commonFd)
26
+ gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, errorFd)
27
+ }
28
+ }
29
+
30
+ func SysLog(s string) {
31
+ t := time.Now()
32
+ _, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
33
+ }
34
+
35
+ func SysError(s string) {
36
+ t := time.Now()
37
+ _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
38
+ }
39
+
40
+ func FatalLog(v ...any) {
41
+ t := time.Now()
42
+ _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v)
43
+ os.Exit(1)
44
+ }
45
+
46
+ func LogQuota(quota int) string {
47
+ if DisplayInCurrencyEnabled {
48
+ return fmt.Sprintf("$%.6f 额度", float64(quota)/QuotaPerUnit)
49
+ } else {
50
+ return fmt.Sprintf("%d 点额度", quota)
51
+ }
52
+ }
common/model-ratio.go ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "encoding/json"
5
+ "strings"
6
+ )
7
+
8
+ // ModelRatio
9
+ // https://platform.openai.com/docs/models/model-endpoint-compatibility
10
+ // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf
11
+ // https://openai.com/pricing
12
+ // TODO: when a new api is enabled, check the pricing here
13
+ // 1 === $0.002 / 1K tokens
14
+ // 1 === ¥0.014 / 1k tokens
15
+ var ModelRatio = map[string]float64{
16
+ "gpt-4": 15,
17
+ "gpt-4-0314": 15,
18
+ "gpt-4-0613": 15,
19
+ "gpt-4-32k": 30,
20
+ "gpt-4-32k-0314": 30,
21
+ "gpt-4-32k-0613": 30,
22
+ "gpt-3.5-turbo": 0.75, // $0.0015 / 1K tokens
23
+ "gpt-3.5-turbo-0301": 0.75,
24
+ "gpt-3.5-turbo-0613": 0.75,
25
+ "gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens
26
+ "gpt-3.5-turbo-16k-0613": 1.5,
27
+ "text-ada-001": 0.2,
28
+ "text-babbage-001": 0.25,
29
+ "text-curie-001": 1,
30
+ "text-davinci-002": 10,
31
+ "text-davinci-003": 10,
32
+ "text-davinci-edit-001": 10,
33
+ "code-davinci-edit-001": 10,
34
+ "whisper-1": 15, // $0.006 / minute -> $0.006 / 150 words -> $0.006 / 200 tokens -> $0.03 / 1k tokens
35
+ "davinci": 10,
36
+ "curie": 10,
37
+ "babbage": 10,
38
+ "ada": 10,
39
+ "text-embedding-ada-002": 0.05,
40
+ "text-search-ada-doc-001": 10,
41
+ "text-moderation-stable": 0.1,
42
+ "text-moderation-latest": 0.1,
43
+ "dall-e": 8,
44
+ "claude-instant-1": 0.815, // $1.63 / 1M tokens
45
+ "claude-2": 5.51, // $11.02 / 1M tokens
46
+ "ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens
47
+ "ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens
48
+ "Embedding-V1": 0.1429, // ¥0.002 / 1k tokens
49
+ "PaLM-2": 1,
50
+ "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
51
+ "chatglm_std": 0.3572, // ¥0.005 / 1k tokens
52
+ "chatglm_lite": 0.1429, // ¥0.002 / 1k tokens
53
+ "qwen-v1": 0.8572, // TBD: https://help.aliyun.com/document_detail/2399482.html?spm=a2c4g.2399482.0.0.1ad347feilAgag
54
+ "qwen-plus-v1": 0.5715, // Same as above
55
+ "SparkDesk": 0.8572, // TBD
56
+ "360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens
57
+ "embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens
58
+ "embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens
59
+ "semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens
60
+ "360GPT_S2_V9.4": 0.8572, // ¥0.012 / 1k tokens
61
+ }
62
+
63
+ func ModelRatio2JSONString() string {
64
+ jsonBytes, err := json.Marshal(ModelRatio)
65
+ if err != nil {
66
+ SysError("error marshalling model ratio: " + err.Error())
67
+ }
68
+ return string(jsonBytes)
69
+ }
70
+
71
+ func UpdateModelRatioByJSONString(jsonStr string) error {
72
+ ModelRatio = make(map[string]float64)
73
+ return json.Unmarshal([]byte(jsonStr), &ModelRatio)
74
+ }
75
+
76
+ func GetModelRatio(name string) float64 {
77
+ ratio, ok := ModelRatio[name]
78
+ if !ok {
79
+ SysError("model ratio not found: " + name)
80
+ return 30
81
+ }
82
+ return ratio
83
+ }
84
+
85
+ func GetCompletionRatio(name string) float64 {
86
+ if strings.HasPrefix(name, "gpt-3.5") {
87
+ return 1.333333
88
+ }
89
+ if strings.HasPrefix(name, "gpt-4") {
90
+ return 2
91
+ }
92
+ if strings.HasPrefix(name, "claude-instant-1") {
93
+ return 3.38
94
+ }
95
+ if strings.HasPrefix(name, "claude-2") {
96
+ return 2.965517
97
+ }
98
+ return 1
99
+ }
common/rate-limit.go ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "sync"
5
+ "time"
6
+ )
7
+
8
+ type InMemoryRateLimiter struct {
9
+ store map[string]*[]int64
10
+ mutex sync.Mutex
11
+ expirationDuration time.Duration
12
+ }
13
+
14
+ func (l *InMemoryRateLimiter) Init(expirationDuration time.Duration) {
15
+ if l.store == nil {
16
+ l.mutex.Lock()
17
+ if l.store == nil {
18
+ l.store = make(map[string]*[]int64)
19
+ l.expirationDuration = expirationDuration
20
+ if expirationDuration > 0 {
21
+ go l.clearExpiredItems()
22
+ }
23
+ }
24
+ l.mutex.Unlock()
25
+ }
26
+ }
27
+
28
+ func (l *InMemoryRateLimiter) clearExpiredItems() {
29
+ for {
30
+ time.Sleep(l.expirationDuration)
31
+ l.mutex.Lock()
32
+ now := time.Now().Unix()
33
+ for key := range l.store {
34
+ queue := l.store[key]
35
+ size := len(*queue)
36
+ if size == 0 || now-(*queue)[size-1] > int64(l.expirationDuration.Seconds()) {
37
+ delete(l.store, key)
38
+ }
39
+ }
40
+ l.mutex.Unlock()
41
+ }
42
+ }
43
+
44
+ // Request parameter duration's unit is seconds
45
+ func (l *InMemoryRateLimiter) Request(key string, maxRequestNum int, duration int64) bool {
46
+ l.mutex.Lock()
47
+ defer l.mutex.Unlock()
48
+ // [old <-- new]
49
+ queue, ok := l.store[key]
50
+ now := time.Now().Unix()
51
+ if ok {
52
+ if len(*queue) < maxRequestNum {
53
+ *queue = append(*queue, now)
54
+ return true
55
+ } else {
56
+ if now-(*queue)[0] >= duration {
57
+ *queue = (*queue)[1:]
58
+ *queue = append(*queue, now)
59
+ return true
60
+ } else {
61
+ return false
62
+ }
63
+ }
64
+ } else {
65
+ s := make([]int64, 0, maxRequestNum)
66
+ l.store[key] = &s
67
+ *(l.store[key]) = append(*(l.store[key]), now)
68
+ }
69
+ return true
70
+ }
common/redis.go ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "context"
5
+ "github.com/go-redis/redis/v8"
6
+ "os"
7
+ "time"
8
+ )
9
+
10
+ var RDB *redis.Client
11
+ var RedisEnabled = true
12
+
13
+ // InitRedisClient This function is called after init()
14
+ func InitRedisClient() (err error) {
15
+ if os.Getenv("REDIS_CONN_STRING") == "" {
16
+ RedisEnabled = false
17
+ SysLog("REDIS_CONN_STRING not set, Redis is not enabled")
18
+ return nil
19
+ }
20
+ if os.Getenv("SYNC_FREQUENCY") == "" {
21
+ RedisEnabled = false
22
+ SysLog("SYNC_FREQUENCY not set, Redis is disabled")
23
+ return nil
24
+ }
25
+ SysLog("Redis is enabled")
26
+ opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING"))
27
+ if err != nil {
28
+ FatalLog("failed to parse Redis connection string: " + err.Error())
29
+ }
30
+ RDB = redis.NewClient(opt)
31
+
32
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
33
+ defer cancel()
34
+
35
+ _, err = RDB.Ping(ctx).Result()
36
+ if err != nil {
37
+ FatalLog("Redis ping test failed: " + err.Error())
38
+ }
39
+ return err
40
+ }
41
+
42
+ func ParseRedisOption() *redis.Options {
43
+ opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING"))
44
+ if err != nil {
45
+ FatalLog("failed to parse Redis connection string: " + err.Error())
46
+ }
47
+ return opt
48
+ }
49
+
50
+ func RedisSet(key string, value string, expiration time.Duration) error {
51
+ ctx := context.Background()
52
+ return RDB.Set(ctx, key, value, expiration).Err()
53
+ }
54
+
55
+ func RedisGet(key string) (string, error) {
56
+ ctx := context.Background()
57
+ return RDB.Get(ctx, key).Result()
58
+ }
59
+
60
+ func RedisDel(key string) error {
61
+ ctx := context.Background()
62
+ return RDB.Del(ctx, key).Err()
63
+ }
64
+
65
+ func RedisDecrease(key string, value int64) error {
66
+ ctx := context.Background()
67
+ return RDB.DecrBy(ctx, key, value).Err()
68
+ }
common/utils.go ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "fmt"
5
+ "github.com/google/uuid"
6
+ "html/template"
7
+ "log"
8
+ "math/rand"
9
+ "net"
10
+ "os"
11
+ "os/exec"
12
+ "runtime"
13
+ "strconv"
14
+ "strings"
15
+ "time"
16
+ )
17
+
18
+ func OpenBrowser(url string) {
19
+ var err error
20
+
21
+ switch runtime.GOOS {
22
+ case "linux":
23
+ err = exec.Command("xdg-open", url).Start()
24
+ case "windows":
25
+ err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
26
+ case "darwin":
27
+ err = exec.Command("open", url).Start()
28
+ }
29
+ if err != nil {
30
+ log.Println(err)
31
+ }
32
+ }
33
+
34
+ func GetIp() (ip string) {
35
+ ips, err := net.InterfaceAddrs()
36
+ if err != nil {
37
+ log.Println(err)
38
+ return ip
39
+ }
40
+
41
+ for _, a := range ips {
42
+ if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
43
+ if ipNet.IP.To4() != nil {
44
+ ip = ipNet.IP.String()
45
+ if strings.HasPrefix(ip, "10") {
46
+ return
47
+ }
48
+ if strings.HasPrefix(ip, "172") {
49
+ return
50
+ }
51
+ if strings.HasPrefix(ip, "192.168") {
52
+ return
53
+ }
54
+ ip = ""
55
+ }
56
+ }
57
+ }
58
+ return
59
+ }
60
+
61
+ var sizeKB = 1024
62
+ var sizeMB = sizeKB * 1024
63
+ var sizeGB = sizeMB * 1024
64
+
65
+ func Bytes2Size(num int64) string {
66
+ numStr := ""
67
+ unit := "B"
68
+ if num/int64(sizeGB) > 1 {
69
+ numStr = fmt.Sprintf("%.2f", float64(num)/float64(sizeGB))
70
+ unit = "GB"
71
+ } else if num/int64(sizeMB) > 1 {
72
+ numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeMB)))
73
+ unit = "MB"
74
+ } else if num/int64(sizeKB) > 1 {
75
+ numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeKB)))
76
+ unit = "KB"
77
+ } else {
78
+ numStr = fmt.Sprintf("%d", num)
79
+ }
80
+ return numStr + " " + unit
81
+ }
82
+
83
+ func Seconds2Time(num int) (time string) {
84
+ if num/31104000 > 0 {
85
+ time += strconv.Itoa(num/31104000) + " 年 "
86
+ num %= 31104000
87
+ }
88
+ if num/2592000 > 0 {
89
+ time += strconv.Itoa(num/2592000) + " 个月 "
90
+ num %= 2592000
91
+ }
92
+ if num/86400 > 0 {
93
+ time += strconv.Itoa(num/86400) + " 天 "
94
+ num %= 86400
95
+ }
96
+ if num/3600 > 0 {
97
+ time += strconv.Itoa(num/3600) + " 小时 "
98
+ num %= 3600
99
+ }
100
+ if num/60 > 0 {
101
+ time += strconv.Itoa(num/60) + " 分钟 "
102
+ num %= 60
103
+ }
104
+ time += strconv.Itoa(num) + " 秒"
105
+ return
106
+ }
107
+
108
+ func Interface2String(inter interface{}) string {
109
+ switch inter.(type) {
110
+ case string:
111
+ return inter.(string)
112
+ case int:
113
+ return fmt.Sprintf("%d", inter.(int))
114
+ case float64:
115
+ return fmt.Sprintf("%f", inter.(float64))
116
+ }
117
+ return "Not Implemented"
118
+ }
119
+
120
+ func UnescapeHTML(x string) interface{} {
121
+ return template.HTML(x)
122
+ }
123
+
124
+ func IntMax(a int, b int) int {
125
+ if a >= b {
126
+ return a
127
+ } else {
128
+ return b
129
+ }
130
+ }
131
+
132
+ func GetUUID() string {
133
+ code := uuid.New().String()
134
+ code = strings.Replace(code, "-", "", -1)
135
+ return code
136
+ }
137
+
138
+ const keyChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
139
+
140
+ func init() {
141
+ rand.Seed(time.Now().UnixNano())
142
+ }
143
+
144
+ func GenerateKey() string {
145
+ rand.Seed(time.Now().UnixNano())
146
+ key := make([]byte, 48)
147
+ for i := 0; i < 16; i++ {
148
+ key[i] = keyChars[rand.Intn(len(keyChars))]
149
+ }
150
+ uuid_ := GetUUID()
151
+ for i := 0; i < 32; i++ {
152
+ c := uuid_[i]
153
+ if i%2 == 0 && c >= 'a' && c <= 'z' {
154
+ c = c - 'a' + 'A'
155
+ }
156
+ key[i+16] = c
157
+ }
158
+ return string(key)
159
+ }
160
+
161
+ func GetRandomString(length int) string {
162
+ rand.Seed(time.Now().UnixNano())
163
+ key := make([]byte, length)
164
+ for i := 0; i < length; i++ {
165
+ key[i] = keyChars[rand.Intn(len(keyChars))]
166
+ }
167
+ return string(key)
168
+ }
169
+
170
+ func GetTimestamp() int64 {
171
+ return time.Now().Unix()
172
+ }
173
+
174
+ func Max(a int, b int) int {
175
+ if a >= b {
176
+ return a
177
+ } else {
178
+ return b
179
+ }
180
+ }
181
+
182
+ func GetOrDefault(env string, defaultValue int) int {
183
+ if env == "" || os.Getenv(env) == "" {
184
+ return defaultValue
185
+ }
186
+ num, err := strconv.Atoi(os.Getenv(env))
187
+ if err != nil {
188
+ SysError(fmt.Sprintf("failed to parse %s: %s, using default value: %d", env, err.Error(), defaultValue))
189
+ return defaultValue
190
+ }
191
+ return num
192
+ }
common/validate.go ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "github.com/go-playground/validator/v10"
4
+
5
+ var Validate *validator.Validate
6
+
7
+ func init() {
8
+ Validate = validator.New()
9
+ }
common/verification.go ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "github.com/google/uuid"
5
+ "strings"
6
+ "sync"
7
+ "time"
8
+ )
9
+
10
+ type verificationValue struct {
11
+ code string
12
+ time time.Time
13
+ }
14
+
15
+ const (
16
+ EmailVerificationPurpose = "v"
17
+ PasswordResetPurpose = "r"
18
+ )
19
+
20
+ var verificationMutex sync.Mutex
21
+ var verificationMap map[string]verificationValue
22
+ var verificationMapMaxSize = 10
23
+ var VerificationValidMinutes = 10
24
+
25
+ func GenerateVerificationCode(length int) string {
26
+ code := uuid.New().String()
27
+ code = strings.Replace(code, "-", "", -1)
28
+ if length == 0 {
29
+ return code
30
+ }
31
+ return code[:length]
32
+ }
33
+
34
+ func RegisterVerificationCodeWithKey(key string, code string, purpose string) {
35
+ verificationMutex.Lock()
36
+ defer verificationMutex.Unlock()
37
+ verificationMap[purpose+key] = verificationValue{
38
+ code: code,
39
+ time: time.Now(),
40
+ }
41
+ if len(verificationMap) > verificationMapMaxSize {
42
+ removeExpiredPairs()
43
+ }
44
+ }
45
+
46
+ func VerifyCodeWithKey(key string, code string, purpose string) bool {
47
+ verificationMutex.Lock()
48
+ defer verificationMutex.Unlock()
49
+ value, okay := verificationMap[purpose+key]
50
+ now := time.Now()
51
+ if !okay || int(now.Sub(value.time).Seconds()) >= VerificationValidMinutes*60 {
52
+ return false
53
+ }
54
+ return code == value.code
55
+ }
56
+
57
+ func DeleteKey(key string, purpose string) {
58
+ verificationMutex.Lock()
59
+ defer verificationMutex.Unlock()
60
+ delete(verificationMap, purpose+key)
61
+ }
62
+
63
+ // no lock inside, so the caller must lock the verificationMap before calling!
64
+ func removeExpiredPairs() {
65
+ now := time.Now()
66
+ for key := range verificationMap {
67
+ if int(now.Sub(verificationMap[key].time).Seconds()) >= VerificationValidMinutes*60 {
68
+ delete(verificationMap, key)
69
+ }
70
+ }
71
+ }
72
+
73
+ func init() {
74
+ verificationMutex.Lock()
75
+ defer verificationMutex.Unlock()
76
+ verificationMap = make(map[string]verificationValue)
77
+ }
controller/billing.go ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+ "one-api/common"
6
+ "one-api/model"
7
+ )
8
+
9
+ func GetSubscription(c *gin.Context) {
10
+ var remainQuota int
11
+ var usedQuota int
12
+ var err error
13
+ var token *model.Token
14
+ var expiredTime int64
15
+ if common.DisplayTokenStatEnabled {
16
+ tokenId := c.GetInt("token_id")
17
+ token, err = model.GetTokenById(tokenId)
18
+ expiredTime = token.ExpiredTime
19
+ remainQuota = token.RemainQuota
20
+ usedQuota = token.UsedQuota
21
+ } else {
22
+ userId := c.GetInt("id")
23
+ remainQuota, err = model.GetUserQuota(userId)
24
+ usedQuota, err = model.GetUserUsedQuota(userId)
25
+ }
26
+ if expiredTime <= 0 {
27
+ expiredTime = 0
28
+ }
29
+ if err != nil {
30
+ openAIError := OpenAIError{
31
+ Message: err.Error(),
32
+ Type: "one_api_error",
33
+ }
34
+ c.JSON(200, gin.H{
35
+ "error": openAIError,
36
+ })
37
+ return
38
+ }
39
+ quota := remainQuota + usedQuota
40
+ amount := float64(quota)
41
+ if common.DisplayInCurrencyEnabled {
42
+ amount /= common.QuotaPerUnit
43
+ }
44
+ if token != nil && token.UnlimitedQuota {
45
+ amount = 100000000
46
+ }
47
+ subscription := OpenAISubscriptionResponse{
48
+ Object: "billing_subscription",
49
+ HasPaymentMethod: true,
50
+ SoftLimitUSD: amount,
51
+ HardLimitUSD: amount,
52
+ SystemHardLimitUSD: amount,
53
+ AccessUntil: expiredTime,
54
+ }
55
+ c.JSON(200, subscription)
56
+ return
57
+ }
58
+
59
+ func GetUsage(c *gin.Context) {
60
+ var quota int
61
+ var err error
62
+ var token *model.Token
63
+ if common.DisplayTokenStatEnabled {
64
+ tokenId := c.GetInt("token_id")
65
+ token, err = model.GetTokenById(tokenId)
66
+ quota = token.UsedQuota
67
+ } else {
68
+ userId := c.GetInt("id")
69
+ quota, err = model.GetUserUsedQuota(userId)
70
+ }
71
+ if err != nil {
72
+ openAIError := OpenAIError{
73
+ Message: err.Error(),
74
+ Type: "one_api_error",
75
+ }
76
+ c.JSON(200, gin.H{
77
+ "error": openAIError,
78
+ })
79
+ return
80
+ }
81
+ amount := float64(quota)
82
+ if common.DisplayInCurrencyEnabled {
83
+ amount /= common.QuotaPerUnit
84
+ }
85
+ usage := OpenAIUsageResponse{
86
+ Object: "list",
87
+ TotalUsage: amount * 100,
88
+ }
89
+ c.JSON(200, usage)
90
+ return
91
+ }
controller/channel-billing.go ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "fmt"
7
+ "io"
8
+ "net/http"
9
+ "one-api/common"
10
+ "one-api/model"
11
+ "strconv"
12
+ "time"
13
+
14
+ "github.com/gin-gonic/gin"
15
+ )
16
+
17
+ // https://github.com/songquanpeng/one-api/issues/79
18
+
19
+ type OpenAISubscriptionResponse struct {
20
+ Object string `json:"object"`
21
+ HasPaymentMethod bool `json:"has_payment_method"`
22
+ SoftLimitUSD float64 `json:"soft_limit_usd"`
23
+ HardLimitUSD float64 `json:"hard_limit_usd"`
24
+ SystemHardLimitUSD float64 `json:"system_hard_limit_usd"`
25
+ AccessUntil int64 `json:"access_until"`
26
+ }
27
+
28
+ type OpenAIUsageDailyCost struct {
29
+ Timestamp float64 `json:"timestamp"`
30
+ LineItems []struct {
31
+ Name string `json:"name"`
32
+ Cost float64 `json:"cost"`
33
+ }
34
+ }
35
+
36
+ type OpenAICreditGrants struct {
37
+ Object string `json:"object"`
38
+ TotalGranted float64 `json:"total_granted"`
39
+ TotalUsed float64 `json:"total_used"`
40
+ TotalAvailable float64 `json:"total_available"`
41
+ }
42
+
43
+ type OpenAIUsageResponse struct {
44
+ Object string `json:"object"`
45
+ //DailyCosts []OpenAIUsageDailyCost `json:"daily_costs"`
46
+ TotalUsage float64 `json:"total_usage"` // unit: 0.01 dollar
47
+ }
48
+
49
+ type OpenAISBUsageResponse struct {
50
+ Msg string `json:"msg"`
51
+ Data *struct {
52
+ Credit string `json:"credit"`
53
+ } `json:"data"`
54
+ }
55
+
56
+ type AIProxyUserOverviewResponse struct {
57
+ Success bool `json:"success"`
58
+ Message string `json:"message"`
59
+ ErrorCode int `json:"error_code"`
60
+ Data struct {
61
+ TotalPoints float64 `json:"totalPoints"`
62
+ } `json:"data"`
63
+ }
64
+
65
+ type API2GPTUsageResponse struct {
66
+ Object string `json:"object"`
67
+ TotalGranted float64 `json:"total_granted"`
68
+ TotalUsed float64 `json:"total_used"`
69
+ TotalRemaining float64 `json:"total_remaining"`
70
+ }
71
+
72
+ type APGC2DGPTUsageResponse struct {
73
+ //Grants interface{} `json:"grants"`
74
+ Object string `json:"object"`
75
+ TotalAvailable float64 `json:"total_available"`
76
+ TotalGranted float64 `json:"total_granted"`
77
+ TotalUsed float64 `json:"total_used"`
78
+ }
79
+
80
+ // GetAuthHeader get auth header
81
+ func GetAuthHeader(token string) http.Header {
82
+ h := http.Header{}
83
+ h.Add("Authorization", fmt.Sprintf("Bearer %s", token))
84
+ return h
85
+ }
86
+
87
+ func GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {
88
+ req, err := http.NewRequest(method, url, nil)
89
+ if err != nil {
90
+ return nil, err
91
+ }
92
+ for k := range headers {
93
+ req.Header.Add(k, headers.Get(k))
94
+ }
95
+ res, err := httpClient.Do(req)
96
+ if err != nil {
97
+ return nil, err
98
+ }
99
+ if res.StatusCode != http.StatusOK {
100
+ return nil, fmt.Errorf("status code: %d", res.StatusCode)
101
+ }
102
+ body, err := io.ReadAll(res.Body)
103
+ if err != nil {
104
+ return nil, err
105
+ }
106
+ err = res.Body.Close()
107
+ if err != nil {
108
+ return nil, err
109
+ }
110
+ return body, nil
111
+ }
112
+
113
+ func updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {
114
+ url := fmt.Sprintf("%s/dashboard/billing/credit_grants", channel.BaseURL)
115
+ body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
116
+
117
+ if err != nil {
118
+ return 0, err
119
+ }
120
+ response := OpenAICreditGrants{}
121
+ err = json.Unmarshal(body, &response)
122
+ if err != nil {
123
+ return 0, err
124
+ }
125
+ channel.UpdateBalance(response.TotalAvailable)
126
+ return response.TotalAvailable, nil
127
+ }
128
+
129
+ func updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {
130
+ url := fmt.Sprintf("https://api.openai-sb.com/sb-api/user/status?api_key=%s", channel.Key)
131
+ body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
132
+ if err != nil {
133
+ return 0, err
134
+ }
135
+ response := OpenAISBUsageResponse{}
136
+ err = json.Unmarshal(body, &response)
137
+ if err != nil {
138
+ return 0, err
139
+ }
140
+ if response.Data == nil {
141
+ return 0, errors.New(response.Msg)
142
+ }
143
+ balance, err := strconv.ParseFloat(response.Data.Credit, 64)
144
+ if err != nil {
145
+ return 0, err
146
+ }
147
+ channel.UpdateBalance(balance)
148
+ return balance, nil
149
+ }
150
+
151
+ func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {
152
+ url := "https://aiproxy.io/api/report/getUserOverview"
153
+ headers := http.Header{}
154
+ headers.Add("Api-Key", channel.Key)
155
+ body, err := GetResponseBody("GET", url, channel, headers)
156
+ if err != nil {
157
+ return 0, err
158
+ }
159
+ response := AIProxyUserOverviewResponse{}
160
+ err = json.Unmarshal(body, &response)
161
+ if err != nil {
162
+ return 0, err
163
+ }
164
+ if !response.Success {
165
+ return 0, fmt.Errorf("code: %d, message: %s", response.ErrorCode, response.Message)
166
+ }
167
+ channel.UpdateBalance(response.Data.TotalPoints)
168
+ return response.Data.TotalPoints, nil
169
+ }
170
+
171
+ func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
172
+ url := "https://api.api2gpt.com/dashboard/billing/credit_grants"
173
+ body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
174
+
175
+ if err != nil {
176
+ return 0, err
177
+ }
178
+ response := API2GPTUsageResponse{}
179
+ err = json.Unmarshal(body, &response)
180
+ if err != nil {
181
+ return 0, err
182
+ }
183
+ channel.UpdateBalance(response.TotalRemaining)
184
+ return response.TotalRemaining, nil
185
+ }
186
+
187
+ func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
188
+ url := "https://api.aigc2d.com/dashboard/billing/credit_grants"
189
+ body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
190
+ if err != nil {
191
+ return 0, err
192
+ }
193
+ response := APGC2DGPTUsageResponse{}
194
+ err = json.Unmarshal(body, &response)
195
+ if err != nil {
196
+ return 0, err
197
+ }
198
+ channel.UpdateBalance(response.TotalAvailable)
199
+ return response.TotalAvailable, nil
200
+ }
201
+
202
+ func updateChannelBalance(channel *model.Channel) (float64, error) {
203
+ baseURL := common.ChannelBaseURLs[channel.Type]
204
+ if channel.BaseURL == "" {
205
+ channel.BaseURL = baseURL
206
+ }
207
+ switch channel.Type {
208
+ case common.ChannelTypeOpenAI:
209
+ if channel.BaseURL != "" {
210
+ baseURL = channel.BaseURL
211
+ }
212
+ case common.ChannelTypeAzure:
213
+ return 0, errors.New("尚未实现")
214
+ case common.ChannelTypeCustom:
215
+ baseURL = channel.BaseURL
216
+ case common.ChannelTypeCloseAI:
217
+ return updateChannelCloseAIBalance(channel)
218
+ case common.ChannelTypeOpenAISB:
219
+ return updateChannelOpenAISBBalance(channel)
220
+ case common.ChannelTypeAIProxy:
221
+ return updateChannelAIProxyBalance(channel)
222
+ case common.ChannelTypeAPI2GPT:
223
+ return updateChannelAPI2GPTBalance(channel)
224
+ case common.ChannelTypeAIGC2D:
225
+ return updateChannelAIGC2DBalance(channel)
226
+ default:
227
+ return 0, errors.New("尚未实现")
228
+ }
229
+ url := fmt.Sprintf("%s/v1/dashboard/billing/subscription", baseURL)
230
+
231
+ body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
232
+ if err != nil {
233
+ return 0, err
234
+ }
235
+ subscription := OpenAISubscriptionResponse{}
236
+ err = json.Unmarshal(body, &subscription)
237
+ if err != nil {
238
+ return 0, err
239
+ }
240
+ now := time.Now()
241
+ startDate := fmt.Sprintf("%s-01", now.Format("2006-01"))
242
+ endDate := now.Format("2006-01-02")
243
+ if !subscription.HasPaymentMethod {
244
+ startDate = now.AddDate(0, 0, -100).Format("2006-01-02")
245
+ }
246
+ url = fmt.Sprintf("%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s", baseURL, startDate, endDate)
247
+ body, err = GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
248
+ if err != nil {
249
+ return 0, err
250
+ }
251
+ usage := OpenAIUsageResponse{}
252
+ err = json.Unmarshal(body, &usage)
253
+ if err != nil {
254
+ return 0, err
255
+ }
256
+ balance := subscription.HardLimitUSD - usage.TotalUsage/100
257
+ channel.UpdateBalance(balance)
258
+ return balance, nil
259
+ }
260
+
261
+ func UpdateChannelBalance(c *gin.Context) {
262
+ id, err := strconv.Atoi(c.Param("id"))
263
+ if err != nil {
264
+ c.JSON(http.StatusOK, gin.H{
265
+ "success": false,
266
+ "message": err.Error(),
267
+ })
268
+ return
269
+ }
270
+ channel, err := model.GetChannelById(id, true)
271
+ if err != nil {
272
+ c.JSON(http.StatusOK, gin.H{
273
+ "success": false,
274
+ "message": err.Error(),
275
+ })
276
+ return
277
+ }
278
+ balance, err := updateChannelBalance(channel)
279
+ if err != nil {
280
+ c.JSON(http.StatusOK, gin.H{
281
+ "success": false,
282
+ "message": err.Error(),
283
+ })
284
+ return
285
+ }
286
+ c.JSON(http.StatusOK, gin.H{
287
+ "success": true,
288
+ "message": "",
289
+ "balance": balance,
290
+ })
291
+ return
292
+ }
293
+
294
+ func updateAllChannelsBalance() error {
295
+ channels, err := model.GetAllChannels(0, 0, true)
296
+ if err != nil {
297
+ return err
298
+ }
299
+ for _, channel := range channels {
300
+ if channel.Status != common.ChannelStatusEnabled {
301
+ continue
302
+ }
303
+ // TODO: support Azure
304
+ if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {
305
+ continue
306
+ }
307
+ balance, err := updateChannelBalance(channel)
308
+ if err != nil {
309
+ continue
310
+ } else {
311
+ // err is nil & balance <= 0 means quota is used up
312
+ if balance <= 0 {
313
+ disableChannel(channel.Id, channel.Name, "余额不足")
314
+ }
315
+ }
316
+ time.Sleep(common.RequestInterval)
317
+ }
318
+ return nil
319
+ }
320
+
321
+ func UpdateAllChannelsBalance(c *gin.Context) {
322
+ // TODO: make it async
323
+ err := updateAllChannelsBalance()
324
+ if err != nil {
325
+ c.JSON(http.StatusOK, gin.H{
326
+ "success": false,
327
+ "message": err.Error(),
328
+ })
329
+ return
330
+ }
331
+ c.JSON(http.StatusOK, gin.H{
332
+ "success": true,
333
+ "message": "",
334
+ })
335
+ return
336
+ }
337
+
338
+ func AutomaticallyUpdateChannels(frequency int) {
339
+ for {
340
+ time.Sleep(time.Duration(frequency) * time.Minute)
341
+ common.SysLog("updating all channels")
342
+ _ = updateAllChannelsBalance()
343
+ common.SysLog("channels update done")
344
+ }
345
+ }
controller/channel-test.go ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "github.com/gin-gonic/gin"
9
+ "net/http"
10
+ "one-api/common"
11
+ "one-api/model"
12
+ "strconv"
13
+ "sync"
14
+ "time"
15
+ )
16
+
17
+ func testChannel(channel *model.Channel, request ChatRequest) (error, *OpenAIError) {
18
+ switch channel.Type {
19
+ case common.ChannelTypePaLM:
20
+ fallthrough
21
+ case common.ChannelTypeAnthropic:
22
+ fallthrough
23
+ case common.ChannelTypeBaidu:
24
+ fallthrough
25
+ case common.ChannelTypeZhipu:
26
+ fallthrough
27
+ case common.ChannelTypeAli:
28
+ fallthrough
29
+ case common.ChannelType360:
30
+ fallthrough
31
+ case common.ChannelTypeXunfei:
32
+ return errors.New("该渠道类型当前版本不支持测试,请手动测试"), nil
33
+ case common.ChannelTypeAzure:
34
+ request.Model = "gpt-35-turbo"
35
+ default:
36
+ request.Model = "gpt-3.5-turbo"
37
+ }
38
+ requestURL := common.ChannelBaseURLs[channel.Type]
39
+ if channel.Type == common.ChannelTypeAzure {
40
+ requestURL = fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=2023-03-15-preview", channel.BaseURL, request.Model)
41
+ } else {
42
+ if channel.BaseURL != "" {
43
+ requestURL = channel.BaseURL
44
+ }
45
+ requestURL += "/v1/chat/completions"
46
+ }
47
+
48
+ jsonData, err := json.Marshal(request)
49
+ if err != nil {
50
+ return err, nil
51
+ }
52
+ req, err := http.NewRequest("POST", requestURL, bytes.NewBuffer(jsonData))
53
+ if err != nil {
54
+ return err, nil
55
+ }
56
+ if channel.Type == common.ChannelTypeAzure {
57
+ req.Header.Set("api-key", channel.Key)
58
+ } else {
59
+ req.Header.Set("Authorization", "Bearer "+channel.Key)
60
+ }
61
+ req.Header.Set("Content-Type", "application/json")
62
+ resp, err := httpClient.Do(req)
63
+ if err != nil {
64
+ return err, nil
65
+ }
66
+ defer resp.Body.Close()
67
+ var response TextResponse
68
+ err = json.NewDecoder(resp.Body).Decode(&response)
69
+ if err != nil {
70
+ return err, nil
71
+ }
72
+ if response.Usage.CompletionTokens == 0 {
73
+ return errors.New(fmt.Sprintf("type %s, code %v, message %s", response.Error.Type, response.Error.Code, response.Error.Message)), &response.Error
74
+ }
75
+ return nil, nil
76
+ }
77
+
78
+ func buildTestRequest() *ChatRequest {
79
+ testRequest := &ChatRequest{
80
+ Model: "", // this will be set later
81
+ MaxTokens: 1,
82
+ }
83
+ testMessage := Message{
84
+ Role: "user",
85
+ Content: "hi",
86
+ }
87
+ testRequest.Messages = append(testRequest.Messages, testMessage)
88
+ return testRequest
89
+ }
90
+
91
+ func TestChannel(c *gin.Context) {
92
+ id, err := strconv.Atoi(c.Param("id"))
93
+ if err != nil {
94
+ c.JSON(http.StatusOK, gin.H{
95
+ "success": false,
96
+ "message": err.Error(),
97
+ })
98
+ return
99
+ }
100
+ channel, err := model.GetChannelById(id, true)
101
+ if err != nil {
102
+ c.JSON(http.StatusOK, gin.H{
103
+ "success": false,
104
+ "message": err.Error(),
105
+ })
106
+ return
107
+ }
108
+ testRequest := buildTestRequest()
109
+ tik := time.Now()
110
+ err, _ = testChannel(channel, *testRequest)
111
+ tok := time.Now()
112
+ milliseconds := tok.Sub(tik).Milliseconds()
113
+ go channel.UpdateResponseTime(milliseconds)
114
+ consumedTime := float64(milliseconds) / 1000.0
115
+ if err != nil {
116
+ c.JSON(http.StatusOK, gin.H{
117
+ "success": false,
118
+ "message": err.Error(),
119
+ "time": consumedTime,
120
+ })
121
+ return
122
+ }
123
+ c.JSON(http.StatusOK, gin.H{
124
+ "success": true,
125
+ "message": "",
126
+ "time": consumedTime,
127
+ })
128
+ return
129
+ }
130
+
131
+ var testAllChannelsLock sync.Mutex
132
+ var testAllChannelsRunning bool = false
133
+
134
+ // disable & notify
135
+ func disableChannel(channelId int, channelName string, reason string) {
136
+ if common.RootUserEmail == "" {
137
+ common.RootUserEmail = model.GetRootUserEmail()
138
+ }
139
+ model.UpdateChannelStatusById(channelId, common.ChannelStatusDisabled)
140
+ subject := fmt.Sprintf("通道「%s」(#%d)已被禁用", channelName, channelId)
141
+ content := fmt.Sprintf("通道「%s」(#%d)已被禁用,原因:%s", channelName, channelId, reason)
142
+ err := common.SendEmail(subject, common.RootUserEmail, content)
143
+ if err != nil {
144
+ common.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
145
+ }
146
+ }
147
+
148
+ func testAllChannels(notify bool) error {
149
+ if common.RootUserEmail == "" {
150
+ common.RootUserEmail = model.GetRootUserEmail()
151
+ }
152
+ testAllChannelsLock.Lock()
153
+ if testAllChannelsRunning {
154
+ testAllChannelsLock.Unlock()
155
+ return errors.New("测试已在运行中")
156
+ }
157
+ testAllChannelsRunning = true
158
+ testAllChannelsLock.Unlock()
159
+ channels, err := model.GetAllChannels(0, 0, true)
160
+ if err != nil {
161
+ return err
162
+ }
163
+ testRequest := buildTestRequest()
164
+ var disableThreshold = int64(common.ChannelDisableThreshold * 1000)
165
+ if disableThreshold == 0 {
166
+ disableThreshold = 10000000 // a impossible value
167
+ }
168
+ go func() {
169
+ for _, channel := range channels {
170
+ if channel.Status != common.ChannelStatusEnabled {
171
+ continue
172
+ }
173
+ tik := time.Now()
174
+ err, openaiErr := testChannel(channel, *testRequest)
175
+ tok := time.Now()
176
+ milliseconds := tok.Sub(tik).Milliseconds()
177
+ if milliseconds > disableThreshold {
178
+ err = errors.New(fmt.Sprintf("响应时间 %.2fs 超过阈值 %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0))
179
+ disableChannel(channel.Id, channel.Name, err.Error())
180
+ }
181
+ if shouldDisableChannel(openaiErr, -1) {
182
+ disableChannel(channel.Id, channel.Name, err.Error())
183
+ }
184
+ channel.UpdateResponseTime(milliseconds)
185
+ time.Sleep(common.RequestInterval)
186
+ }
187
+ testAllChannelsLock.Lock()
188
+ testAllChannelsRunning = false
189
+ testAllChannelsLock.Unlock()
190
+ if notify {
191
+ err := common.SendEmail("通道测试完成", common.RootUserEmail, "通道测试完成,如果没有收到禁用通知,说明所有通道都正常")
192
+ if err != nil {
193
+ common.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
194
+ }
195
+ }
196
+ }()
197
+ return nil
198
+ }
199
+
200
+ func TestAllChannels(c *gin.Context) {
201
+ err := testAllChannels(true)
202
+ if err != nil {
203
+ c.JSON(http.StatusOK, gin.H{
204
+ "success": false,
205
+ "message": err.Error(),
206
+ })
207
+ return
208
+ }
209
+ c.JSON(http.StatusOK, gin.H{
210
+ "success": true,
211
+ "message": "",
212
+ })
213
+ return
214
+ }
215
+
216
+ func AutomaticallyTestChannels(frequency int) {
217
+ for {
218
+ time.Sleep(time.Duration(frequency) * time.Minute)
219
+ common.SysLog("testing all channels")
220
+ _ = testAllChannels(false)
221
+ common.SysLog("channel test finished")
222
+ }
223
+ }
controller/channel.go ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+ "net/http"
6
+ "one-api/common"
7
+ "one-api/model"
8
+ "strconv"
9
+ "strings"
10
+ )
11
+
12
+ func GetAllChannels(c *gin.Context) {
13
+ p, _ := strconv.Atoi(c.Query("p"))
14
+ if p < 0 {
15
+ p = 0
16
+ }
17
+ channels, err := model.GetAllChannels(p*common.ItemsPerPage, common.ItemsPerPage, false)
18
+ if err != nil {
19
+ c.JSON(http.StatusOK, gin.H{
20
+ "success": false,
21
+ "message": err.Error(),
22
+ })
23
+ return
24
+ }
25
+ c.JSON(http.StatusOK, gin.H{
26
+ "success": true,
27
+ "message": "",
28
+ "data": channels,
29
+ })
30
+ return
31
+ }
32
+
33
+ func SearchChannels(c *gin.Context) {
34
+ keyword := c.Query("keyword")
35
+ channels, err := model.SearchChannels(keyword)
36
+ if err != nil {
37
+ c.JSON(http.StatusOK, gin.H{
38
+ "success": false,
39
+ "message": err.Error(),
40
+ })
41
+ return
42
+ }
43
+ c.JSON(http.StatusOK, gin.H{
44
+ "success": true,
45
+ "message": "",
46
+ "data": channels,
47
+ })
48
+ return
49
+ }
50
+
51
+ func GetChannel(c *gin.Context) {
52
+ id, err := strconv.Atoi(c.Param("id"))
53
+ if err != nil {
54
+ c.JSON(http.StatusOK, gin.H{
55
+ "success": false,
56
+ "message": err.Error(),
57
+ })
58
+ return
59
+ }
60
+ channel, err := model.GetChannelById(id, false)
61
+ if err != nil {
62
+ c.JSON(http.StatusOK, gin.H{
63
+ "success": false,
64
+ "message": err.Error(),
65
+ })
66
+ return
67
+ }
68
+ c.JSON(http.StatusOK, gin.H{
69
+ "success": true,
70
+ "message": "",
71
+ "data": channel,
72
+ })
73
+ return
74
+ }
75
+
76
+ func AddChannel(c *gin.Context) {
77
+ channel := model.Channel{}
78
+ err := c.ShouldBindJSON(&channel)
79
+ if err != nil {
80
+ c.JSON(http.StatusOK, gin.H{
81
+ "success": false,
82
+ "message": err.Error(),
83
+ })
84
+ return
85
+ }
86
+ channel.CreatedTime = common.GetTimestamp()
87
+ keys := strings.Split(channel.Key, "\n")
88
+ channels := make([]model.Channel, 0)
89
+ for _, key := range keys {
90
+ if key == "" {
91
+ continue
92
+ }
93
+ localChannel := channel
94
+ localChannel.Key = key
95
+ channels = append(channels, localChannel)
96
+ }
97
+ err = model.BatchInsertChannels(channels)
98
+ if err != nil {
99
+ c.JSON(http.StatusOK, gin.H{
100
+ "success": false,
101
+ "message": err.Error(),
102
+ })
103
+ return
104
+ }
105
+ c.JSON(http.StatusOK, gin.H{
106
+ "success": true,
107
+ "message": "",
108
+ })
109
+ return
110
+ }
111
+
112
+ func DeleteChannel(c *gin.Context) {
113
+ id, _ := strconv.Atoi(c.Param("id"))
114
+ channel := model.Channel{Id: id}
115
+ err := channel.Delete()
116
+ if err != nil {
117
+ c.JSON(http.StatusOK, gin.H{
118
+ "success": false,
119
+ "message": err.Error(),
120
+ })
121
+ return
122
+ }
123
+ c.JSON(http.StatusOK, gin.H{
124
+ "success": true,
125
+ "message": "",
126
+ })
127
+ return
128
+ }
129
+
130
+ func UpdateChannel(c *gin.Context) {
131
+ channel := model.Channel{}
132
+ err := c.ShouldBindJSON(&channel)
133
+ if err != nil {
134
+ c.JSON(http.StatusOK, gin.H{
135
+ "success": false,
136
+ "message": err.Error(),
137
+ })
138
+ return
139
+ }
140
+ err = channel.Update()
141
+ if err != nil {
142
+ c.JSON(http.StatusOK, gin.H{
143
+ "success": false,
144
+ "message": err.Error(),
145
+ })
146
+ return
147
+ }
148
+ c.JSON(http.StatusOK, gin.H{
149
+ "success": true,
150
+ "message": "",
151
+ "data": channel,
152
+ })
153
+ return
154
+ }
controller/github.go ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "github.com/gin-contrib/sessions"
9
+ "github.com/gin-gonic/gin"
10
+ "net/http"
11
+ "one-api/common"
12
+ "one-api/model"
13
+ "strconv"
14
+ "time"
15
+ )
16
+
17
+ type GitHubOAuthResponse struct {
18
+ AccessToken string `json:"access_token"`
19
+ Scope string `json:"scope"`
20
+ TokenType string `json:"token_type"`
21
+ }
22
+
23
+ type GitHubUser struct {
24
+ Login string `json:"login"`
25
+ Name string `json:"name"`
26
+ Email string `json:"email"`
27
+ }
28
+
29
+ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
30
+ if code == "" {
31
+ return nil, errors.New("无效的参数")
32
+ }
33
+ values := map[string]string{"client_id": common.GitHubClientId, "client_secret": common.GitHubClientSecret, "code": code}
34
+ jsonData, err := json.Marshal(values)
35
+ if err != nil {
36
+ return nil, err
37
+ }
38
+ req, err := http.NewRequest("POST", "https://github.com/login/oauth/access_token", bytes.NewBuffer(jsonData))
39
+ if err != nil {
40
+ return nil, err
41
+ }
42
+ req.Header.Set("Content-Type", "application/json")
43
+ req.Header.Set("Accept", "application/json")
44
+ client := http.Client{
45
+ Timeout: 5 * time.Second,
46
+ }
47
+ res, err := client.Do(req)
48
+ if err != nil {
49
+ common.SysLog(err.Error())
50
+ return nil, errors.New("无法连接至 GitHub 服务器,请稍后重试!")
51
+ }
52
+ defer res.Body.Close()
53
+ var oAuthResponse GitHubOAuthResponse
54
+ err = json.NewDecoder(res.Body).Decode(&oAuthResponse)
55
+ if err != nil {
56
+ return nil, err
57
+ }
58
+ req, err = http.NewRequest("GET", "https://api.github.com/user", nil)
59
+ if err != nil {
60
+ return nil, err
61
+ }
62
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", oAuthResponse.AccessToken))
63
+ res2, err := client.Do(req)
64
+ if err != nil {
65
+ common.SysLog(err.Error())
66
+ return nil, errors.New("无法连接至 GitHub 服务器,请稍后重试!")
67
+ }
68
+ defer res2.Body.Close()
69
+ var githubUser GitHubUser
70
+ err = json.NewDecoder(res2.Body).Decode(&githubUser)
71
+ if err != nil {
72
+ return nil, err
73
+ }
74
+ if githubUser.Login == "" {
75
+ return nil, errors.New("返回值非法,用户字段为空,请稍后重试!")
76
+ }
77
+ return &githubUser, nil
78
+ }
79
+
80
+ func GitHubOAuth(c *gin.Context) {
81
+ session := sessions.Default(c)
82
+ username := session.Get("username")
83
+ if username != nil {
84
+ GitHubBind(c)
85
+ return
86
+ }
87
+
88
+ if !common.GitHubOAuthEnabled {
89
+ c.JSON(http.StatusOK, gin.H{
90
+ "success": false,
91
+ "message": "管理员未开启通过 GitHub 登录以及注册",
92
+ })
93
+ return
94
+ }
95
+ code := c.Query("code")
96
+ githubUser, err := getGitHubUserInfoByCode(code)
97
+ if err != nil {
98
+ c.JSON(http.StatusOK, gin.H{
99
+ "success": false,
100
+ "message": err.Error(),
101
+ })
102
+ return
103
+ }
104
+ user := model.User{
105
+ GitHubId: githubUser.Login,
106
+ }
107
+ if model.IsGitHubIdAlreadyTaken(user.GitHubId) {
108
+ err := user.FillUserByGitHubId()
109
+ if err != nil {
110
+ c.JSON(http.StatusOK, gin.H{
111
+ "success": false,
112
+ "message": err.Error(),
113
+ })
114
+ return
115
+ }
116
+ } else {
117
+ if common.RegisterEnabled {
118
+ user.Username = "github_" + strconv.Itoa(model.GetMaxUserId()+1)
119
+ if githubUser.Name != "" {
120
+ user.DisplayName = githubUser.Name
121
+ } else {
122
+ user.DisplayName = "GitHub User"
123
+ }
124
+ user.Email = githubUser.Email
125
+ user.Role = common.RoleCommonUser
126
+ user.Status = common.UserStatusEnabled
127
+
128
+ if err := user.Insert(0); err != nil {
129
+ c.JSON(http.StatusOK, gin.H{
130
+ "success": false,
131
+ "message": err.Error(),
132
+ })
133
+ return
134
+ }
135
+ } else {
136
+ c.JSON(http.StatusOK, gin.H{
137
+ "success": false,
138
+ "message": "管理员关闭了新用户注册",
139
+ })
140
+ return
141
+ }
142
+ }
143
+
144
+ if user.Status != common.UserStatusEnabled {
145
+ c.JSON(http.StatusOK, gin.H{
146
+ "message": "用户已被封禁",
147
+ "success": false,
148
+ })
149
+ return
150
+ }
151
+ setupLogin(&user, c)
152
+ }
153
+
154
+ func GitHubBind(c *gin.Context) {
155
+ if !common.GitHubOAuthEnabled {
156
+ c.JSON(http.StatusOK, gin.H{
157
+ "success": false,
158
+ "message": "管理员未开启通过 GitHub 登录以及注册",
159
+ })
160
+ return
161
+ }
162
+ code := c.Query("code")
163
+ githubUser, err := getGitHubUserInfoByCode(code)
164
+ if err != nil {
165
+ c.JSON(http.StatusOK, gin.H{
166
+ "success": false,
167
+ "message": err.Error(),
168
+ })
169
+ return
170
+ }
171
+ user := model.User{
172
+ GitHubId: githubUser.Login,
173
+ }
174
+ if model.IsGitHubIdAlreadyTaken(user.GitHubId) {
175
+ c.JSON(http.StatusOK, gin.H{
176
+ "success": false,
177
+ "message": "该 GitHub 账户已被绑定",
178
+ })
179
+ return
180
+ }
181
+ session := sessions.Default(c)
182
+ id := session.Get("id")
183
+ // id := c.GetInt("id") // critical bug!
184
+ user.Id = id.(int)
185
+ err = user.FillUserById()
186
+ if err != nil {
187
+ c.JSON(http.StatusOK, gin.H{
188
+ "success": false,
189
+ "message": err.Error(),
190
+ })
191
+ return
192
+ }
193
+ user.GitHubId = githubUser.Login
194
+ err = user.Update(false)
195
+ if err != nil {
196
+ c.JSON(http.StatusOK, gin.H{
197
+ "success": false,
198
+ "message": err.Error(),
199
+ })
200
+ return
201
+ }
202
+ c.JSON(http.StatusOK, gin.H{
203
+ "success": true,
204
+ "message": "bind",
205
+ })
206
+ return
207
+ }
controller/group.go ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+ "net/http"
6
+ "one-api/common"
7
+ )
8
+
9
+ func GetGroups(c *gin.Context) {
10
+ groupNames := make([]string, 0)
11
+ for groupName, _ := range common.GroupRatio {
12
+ groupNames = append(groupNames, groupName)
13
+ }
14
+ c.JSON(http.StatusOK, gin.H{
15
+ "success": true,
16
+ "message": "",
17
+ "data": groupNames,
18
+ })
19
+ }
controller/log.go ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+ "one-api/common"
6
+ "one-api/model"
7
+ "strconv"
8
+ )
9
+
10
+ func GetAllLogs(c *gin.Context) {
11
+ p, _ := strconv.Atoi(c.Query("p"))
12
+ if p < 0 {
13
+ p = 0
14
+ }
15
+ logType, _ := strconv.Atoi(c.Query("type"))
16
+ startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
17
+ endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
18
+ username := c.Query("username")
19
+ tokenName := c.Query("token_name")
20
+ modelName := c.Query("model_name")
21
+ logs, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, p*common.ItemsPerPage, common.ItemsPerPage)
22
+ if err != nil {
23
+ c.JSON(200, gin.H{
24
+ "success": false,
25
+ "message": err.Error(),
26
+ })
27
+ return
28
+ }
29
+ c.JSON(200, gin.H{
30
+ "success": true,
31
+ "message": "",
32
+ "data": logs,
33
+ })
34
+ }
35
+
36
+ func GetUserLogs(c *gin.Context) {
37
+ p, _ := strconv.Atoi(c.Query("p"))
38
+ if p < 0 {
39
+ p = 0
40
+ }
41
+ userId := c.GetInt("id")
42
+ logType, _ := strconv.Atoi(c.Query("type"))
43
+ startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
44
+ endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
45
+ tokenName := c.Query("token_name")
46
+ modelName := c.Query("model_name")
47
+ logs, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, p*common.ItemsPerPage, common.ItemsPerPage)
48
+ if err != nil {
49
+ c.JSON(200, gin.H{
50
+ "success": false,
51
+ "message": err.Error(),
52
+ })
53
+ return
54
+ }
55
+ c.JSON(200, gin.H{
56
+ "success": true,
57
+ "message": "",
58
+ "data": logs,
59
+ })
60
+ }
61
+
62
+ func SearchAllLogs(c *gin.Context) {
63
+ keyword := c.Query("keyword")
64
+ logs, err := model.SearchAllLogs(keyword)
65
+ if err != nil {
66
+ c.JSON(200, gin.H{
67
+ "success": false,
68
+ "message": err.Error(),
69
+ })
70
+ return
71
+ }
72
+ c.JSON(200, gin.H{
73
+ "success": true,
74
+ "message": "",
75
+ "data": logs,
76
+ })
77
+ }
78
+
79
+ func SearchUserLogs(c *gin.Context) {
80
+ keyword := c.Query("keyword")
81
+ userId := c.GetInt("id")
82
+ logs, err := model.SearchUserLogs(userId, keyword)
83
+ if err != nil {
84
+ c.JSON(200, gin.H{
85
+ "success": false,
86
+ "message": err.Error(),
87
+ })
88
+ return
89
+ }
90
+ c.JSON(200, gin.H{
91
+ "success": true,
92
+ "message": "",
93
+ "data": logs,
94
+ })
95
+ }
96
+
97
+ func GetLogsStat(c *gin.Context) {
98
+ logType, _ := strconv.Atoi(c.Query("type"))
99
+ startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
100
+ endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
101
+ tokenName := c.Query("token_name")
102
+ username := c.Query("username")
103
+ modelName := c.Query("model_name")
104
+ quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName)
105
+ //tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, "")
106
+ c.JSON(200, gin.H{
107
+ "success": true,
108
+ "message": "",
109
+ "data": gin.H{
110
+ "quota": quotaNum,
111
+ //"token": tokenNum,
112
+ },
113
+ })
114
+ }
115
+
116
+ func GetLogsSelfStat(c *gin.Context) {
117
+ username := c.GetString("username")
118
+ logType, _ := strconv.Atoi(c.Query("type"))
119
+ startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
120
+ endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
121
+ tokenName := c.Query("token_name")
122
+ modelName := c.Query("model_name")
123
+ quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName)
124
+ //tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, tokenName)
125
+ c.JSON(200, gin.H{
126
+ "success": true,
127
+ "message": "",
128
+ "data": gin.H{
129
+ "quota": quotaNum,
130
+ //"token": tokenNum,
131
+ },
132
+ })
133
+ }
controller/misc.go ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "net/http"
7
+ "one-api/common"
8
+ "one-api/model"
9
+ "strings"
10
+
11
+ "github.com/gin-gonic/gin"
12
+ )
13
+
14
+ func GetStatus(c *gin.Context) {
15
+ c.JSON(http.StatusOK, gin.H{
16
+ "success": true,
17
+ "message": "",
18
+ "data": gin.H{
19
+ "version": common.Version,
20
+ "start_time": common.StartTime,
21
+ "email_verification": common.EmailVerificationEnabled,
22
+ "github_oauth": common.GitHubOAuthEnabled,
23
+ "github_client_id": common.GitHubClientId,
24
+ "system_name": common.SystemName,
25
+ "logo": common.Logo,
26
+ "footer_html": common.Footer,
27
+ "wechat_qrcode": common.WeChatAccountQRCodeImageURL,
28
+ "wechat_login": common.WeChatAuthEnabled,
29
+ "server_address": common.ServerAddress,
30
+ "turnstile_check": common.TurnstileCheckEnabled,
31
+ "turnstile_site_key": common.TurnstileSiteKey,
32
+ "top_up_link": common.TopUpLink,
33
+ "chat_link": common.ChatLink,
34
+ "quota_per_unit": common.QuotaPerUnit,
35
+ "display_in_currency": common.DisplayInCurrencyEnabled,
36
+ },
37
+ })
38
+ return
39
+ }
40
+
41
+ func GetNotice(c *gin.Context) {
42
+ common.OptionMapRWMutex.RLock()
43
+ defer common.OptionMapRWMutex.RUnlock()
44
+ c.JSON(http.StatusOK, gin.H{
45
+ "success": true,
46
+ "message": "",
47
+ "data": common.OptionMap["Notice"],
48
+ })
49
+ return
50
+ }
51
+
52
+ func GetAbout(c *gin.Context) {
53
+ common.OptionMapRWMutex.RLock()
54
+ defer common.OptionMapRWMutex.RUnlock()
55
+ c.JSON(http.StatusOK, gin.H{
56
+ "success": true,
57
+ "message": "",
58
+ "data": common.OptionMap["About"],
59
+ })
60
+ return
61
+ }
62
+
63
+ func GetHomePageContent(c *gin.Context) {
64
+ common.OptionMapRWMutex.RLock()
65
+ defer common.OptionMapRWMutex.RUnlock()
66
+ c.JSON(http.StatusOK, gin.H{
67
+ "success": true,
68
+ "message": "",
69
+ "data": common.OptionMap["HomePageContent"],
70
+ })
71
+ return
72
+ }
73
+
74
+ func SendEmailVerification(c *gin.Context) {
75
+ email := c.Query("email")
76
+ if err := common.Validate.Var(email, "required,email"); err != nil {
77
+ c.JSON(http.StatusOK, gin.H{
78
+ "success": false,
79
+ "message": "无效的参数",
80
+ })
81
+ return
82
+ }
83
+ if common.EmailDomainRestrictionEnabled {
84
+ allowed := false
85
+ for _, domain := range common.EmailDomainWhitelist {
86
+ if strings.HasSuffix(email, "@"+domain) {
87
+ allowed = true
88
+ break
89
+ }
90
+ }
91
+ if !allowed {
92
+ c.JSON(http.StatusOK, gin.H{
93
+ "success": false,
94
+ "message": "管理员启用了邮箱域名白名单,您的邮箱地址的域名不在白名单中",
95
+ })
96
+ return
97
+ }
98
+ }
99
+ if model.IsEmailAlreadyTaken(email) {
100
+ c.JSON(http.StatusOK, gin.H{
101
+ "success": false,
102
+ "message": "邮箱地址已被占用",
103
+ })
104
+ return
105
+ }
106
+ code := common.GenerateVerificationCode(6)
107
+ common.RegisterVerificationCodeWithKey(email, code, common.EmailVerificationPurpose)
108
+ subject := fmt.Sprintf("%s邮箱验证邮件", common.SystemName)
109
+ content := fmt.Sprintf("<p>您好,你正在进行%s邮箱验证。</p>"+
110
+ "<p>您的验证码为: <strong>%s</strong></p>"+
111
+ "<p>验证码 %d 分钟内有效,如果不是本人操作,请忽略。</p>", common.SystemName, code, common.VerificationValidMinutes)
112
+ err := common.SendEmail(subject, email, content)
113
+ if err != nil {
114
+ c.JSON(http.StatusOK, gin.H{
115
+ "success": false,
116
+ "message": err.Error(),
117
+ })
118
+ return
119
+ }
120
+ c.JSON(http.StatusOK, gin.H{
121
+ "success": true,
122
+ "message": "",
123
+ })
124
+ return
125
+ }
126
+
127
+ func SendPasswordResetEmail(c *gin.Context) {
128
+ email := c.Query("email")
129
+ if err := common.Validate.Var(email, "required,email"); err != nil {
130
+ c.JSON(http.StatusOK, gin.H{
131
+ "success": false,
132
+ "message": "无效的参数",
133
+ })
134
+ return
135
+ }
136
+ if !model.IsEmailAlreadyTaken(email) {
137
+ c.JSON(http.StatusOK, gin.H{
138
+ "success": false,
139
+ "message": "该邮箱地址未注册",
140
+ })
141
+ return
142
+ }
143
+ code := common.GenerateVerificationCode(0)
144
+ common.RegisterVerificationCodeWithKey(email, code, common.PasswordResetPurpose)
145
+ link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", common.ServerAddress, email, code)
146
+ subject := fmt.Sprintf("%s密码重置", common.SystemName)
147
+ content := fmt.Sprintf("<p>您好,你正在进行%s密码重置。</p>"+
148
+ "<p>点击 <a href='%s'>此处</a> 进行密码重置。</p>"+
149
+ "<p>如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:<br> %s </p>"+
150
+ "<p>重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>", common.SystemName, link, link, common.VerificationValidMinutes)
151
+ err := common.SendEmail(subject, email, content)
152
+ if err != nil {
153
+ c.JSON(http.StatusOK, gin.H{
154
+ "success": false,
155
+ "message": err.Error(),
156
+ })
157
+ return
158
+ }
159
+ c.JSON(http.StatusOK, gin.H{
160
+ "success": true,
161
+ "message": "",
162
+ })
163
+ return
164
+ }
165
+
166
+ type PasswordResetRequest struct {
167
+ Email string `json:"email"`
168
+ Token string `json:"token"`
169
+ }
170
+
171
+ func ResetPassword(c *gin.Context) {
172
+ var req PasswordResetRequest
173
+ err := json.NewDecoder(c.Request.Body).Decode(&req)
174
+ if req.Email == "" || req.Token == "" {
175
+ c.JSON(http.StatusOK, gin.H{
176
+ "success": false,
177
+ "message": "无效的参数",
178
+ })
179
+ return
180
+ }
181
+ if !common.VerifyCodeWithKey(req.Email, req.Token, common.PasswordResetPurpose) {
182
+ c.JSON(http.StatusOK, gin.H{
183
+ "success": false,
184
+ "message": "重置链接非法或已过期",
185
+ })
186
+ return
187
+ }
188
+ password := common.GenerateVerificationCode(12)
189
+ err = model.ResetUserPasswordByEmail(req.Email, password)
190
+ if err != nil {
191
+ c.JSON(http.StatusOK, gin.H{
192
+ "success": false,
193
+ "message": err.Error(),
194
+ })
195
+ return
196
+ }
197
+ common.DeleteKey(req.Email, common.PasswordResetPurpose)
198
+ c.JSON(http.StatusOK, gin.H{
199
+ "success": true,
200
+ "message": "",
201
+ "data": password,
202
+ })
203
+ return
204
+ }
controller/model.go ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "fmt"
5
+
6
+ "github.com/gin-gonic/gin"
7
+ )
8
+
9
+ // https://platform.openai.com/docs/api-reference/models/list
10
+
11
+ type OpenAIModelPermission struct {
12
+ Id string `json:"id"`
13
+ Object string `json:"object"`
14
+ Created int `json:"created"`
15
+ AllowCreateEngine bool `json:"allow_create_engine"`
16
+ AllowSampling bool `json:"allow_sampling"`
17
+ AllowLogprobs bool `json:"allow_logprobs"`
18
+ AllowSearchIndices bool `json:"allow_search_indices"`
19
+ AllowView bool `json:"allow_view"`
20
+ AllowFineTuning bool `json:"allow_fine_tuning"`
21
+ Organization string `json:"organization"`
22
+ Group *string `json:"group"`
23
+ IsBlocking bool `json:"is_blocking"`
24
+ }
25
+
26
+ type OpenAIModels struct {
27
+ Id string `json:"id"`
28
+ Object string `json:"object"`
29
+ Created int `json:"created"`
30
+ OwnedBy string `json:"owned_by"`
31
+ Permission []OpenAIModelPermission `json:"permission"`
32
+ Root string `json:"root"`
33
+ Parent *string `json:"parent"`
34
+ }
35
+
36
+ var openAIModels []OpenAIModels
37
+ var openAIModelsMap map[string]OpenAIModels
38
+
39
+ func init() {
40
+ var permission []OpenAIModelPermission
41
+ permission = append(permission, OpenAIModelPermission{
42
+ Id: "modelperm-LwHkVFn8AcMItP432fKKDIKJ",
43
+ Object: "model_permission",
44
+ Created: 1626777600,
45
+ AllowCreateEngine: true,
46
+ AllowSampling: true,
47
+ AllowLogprobs: true,
48
+ AllowSearchIndices: false,
49
+ AllowView: true,
50
+ AllowFineTuning: false,
51
+ Organization: "*",
52
+ Group: nil,
53
+ IsBlocking: false,
54
+ })
55
+ // https://platform.openai.com/docs/models/model-endpoint-compatibility
56
+ openAIModels = []OpenAIModels{
57
+ {
58
+ Id: "dall-e",
59
+ Object: "model",
60
+ Created: 1677649963,
61
+ OwnedBy: "openai",
62
+ Permission: permission,
63
+ Root: "dall-e",
64
+ Parent: nil,
65
+ },
66
+ {
67
+ Id: "whisper-1",
68
+ Object: "model",
69
+ Created: 1677649963,
70
+ OwnedBy: "openai",
71
+ Permission: permission,
72
+ Root: "whisper-1",
73
+ Parent: nil,
74
+ },
75
+ {
76
+ Id: "gpt-3.5-turbo",
77
+ Object: "model",
78
+ Created: 1677649963,
79
+ OwnedBy: "openai",
80
+ Permission: permission,
81
+ Root: "gpt-3.5-turbo",
82
+ Parent: nil,
83
+ },
84
+ {
85
+ Id: "gpt-3.5-turbo-0301",
86
+ Object: "model",
87
+ Created: 1677649963,
88
+ OwnedBy: "openai",
89
+ Permission: permission,
90
+ Root: "gpt-3.5-turbo-0301",
91
+ Parent: nil,
92
+ },
93
+ {
94
+ Id: "gpt-3.5-turbo-0613",
95
+ Object: "model",
96
+ Created: 1677649963,
97
+ OwnedBy: "openai",
98
+ Permission: permission,
99
+ Root: "gpt-3.5-turbo-0613",
100
+ Parent: nil,
101
+ },
102
+ {
103
+ Id: "gpt-3.5-turbo-16k",
104
+ Object: "model",
105
+ Created: 1677649963,
106
+ OwnedBy: "openai",
107
+ Permission: permission,
108
+ Root: "gpt-3.5-turbo-16k",
109
+ Parent: nil,
110
+ },
111
+ {
112
+ Id: "gpt-3.5-turbo-16k-0613",
113
+ Object: "model",
114
+ Created: 1677649963,
115
+ OwnedBy: "openai",
116
+ Permission: permission,
117
+ Root: "gpt-3.5-turbo-16k-0613",
118
+ Parent: nil,
119
+ },
120
+ {
121
+ Id: "gpt-4",
122
+ Object: "model",
123
+ Created: 1677649963,
124
+ OwnedBy: "openai",
125
+ Permission: permission,
126
+ Root: "gpt-4",
127
+ Parent: nil,
128
+ },
129
+ {
130
+ Id: "gpt-4-0314",
131
+ Object: "model",
132
+ Created: 1677649963,
133
+ OwnedBy: "openai",
134
+ Permission: permission,
135
+ Root: "gpt-4-0314",
136
+ Parent: nil,
137
+ },
138
+ {
139
+ Id: "gpt-4-0613",
140
+ Object: "model",
141
+ Created: 1677649963,
142
+ OwnedBy: "openai",
143
+ Permission: permission,
144
+ Root: "gpt-4-0613",
145
+ Parent: nil,
146
+ },
147
+ {
148
+ Id: "gpt-4-32k",
149
+ Object: "model",
150
+ Created: 1677649963,
151
+ OwnedBy: "openai",
152
+ Permission: permission,
153
+ Root: "gpt-4-32k",
154
+ Parent: nil,
155
+ },
156
+ {
157
+ Id: "gpt-4-32k-0314",
158
+ Object: "model",
159
+ Created: 1677649963,
160
+ OwnedBy: "openai",
161
+ Permission: permission,
162
+ Root: "gpt-4-32k-0314",
163
+ Parent: nil,
164
+ },
165
+ {
166
+ Id: "gpt-4-32k-0613",
167
+ Object: "model",
168
+ Created: 1677649963,
169
+ OwnedBy: "openai",
170
+ Permission: permission,
171
+ Root: "gpt-4-32k-0613",
172
+ Parent: nil,
173
+ },
174
+ {
175
+ Id: "text-embedding-ada-002",
176
+ Object: "model",
177
+ Created: 1677649963,
178
+ OwnedBy: "openai",
179
+ Permission: permission,
180
+ Root: "text-embedding-ada-002",
181
+ Parent: nil,
182
+ },
183
+ {
184
+ Id: "text-davinci-003",
185
+ Object: "model",
186
+ Created: 1677649963,
187
+ OwnedBy: "openai",
188
+ Permission: permission,
189
+ Root: "text-davinci-003",
190
+ Parent: nil,
191
+ },
192
+ {
193
+ Id: "text-davinci-002",
194
+ Object: "model",
195
+ Created: 1677649963,
196
+ OwnedBy: "openai",
197
+ Permission: permission,
198
+ Root: "text-davinci-002",
199
+ Parent: nil,
200
+ },
201
+ {
202
+ Id: "text-curie-001",
203
+ Object: "model",
204
+ Created: 1677649963,
205
+ OwnedBy: "openai",
206
+ Permission: permission,
207
+ Root: "text-curie-001",
208
+ Parent: nil,
209
+ },
210
+ {
211
+ Id: "text-babbage-001",
212
+ Object: "model",
213
+ Created: 1677649963,
214
+ OwnedBy: "openai",
215
+ Permission: permission,
216
+ Root: "text-babbage-001",
217
+ Parent: nil,
218
+ },
219
+ {
220
+ Id: "text-ada-001",
221
+ Object: "model",
222
+ Created: 1677649963,
223
+ OwnedBy: "openai",
224
+ Permission: permission,
225
+ Root: "text-ada-001",
226
+ Parent: nil,
227
+ },
228
+ {
229
+ Id: "text-moderation-latest",
230
+ Object: "model",
231
+ Created: 1677649963,
232
+ OwnedBy: "openai",
233
+ Permission: permission,
234
+ Root: "text-moderation-latest",
235
+ Parent: nil,
236
+ },
237
+ {
238
+ Id: "text-moderation-stable",
239
+ Object: "model",
240
+ Created: 1677649963,
241
+ OwnedBy: "openai",
242
+ Permission: permission,
243
+ Root: "text-moderation-stable",
244
+ Parent: nil,
245
+ },
246
+ {
247
+ Id: "text-davinci-edit-001",
248
+ Object: "model",
249
+ Created: 1677649963,
250
+ OwnedBy: "openai",
251
+ Permission: permission,
252
+ Root: "text-davinci-edit-001",
253
+ Parent: nil,
254
+ },
255
+ {
256
+ Id: "code-davinci-edit-001",
257
+ Object: "model",
258
+ Created: 1677649963,
259
+ OwnedBy: "openai",
260
+ Permission: permission,
261
+ Root: "code-davinci-edit-001",
262
+ Parent: nil,
263
+ },
264
+ {
265
+ Id: "claude-instant-1",
266
+ Object: "model",
267
+ Created: 1677649963,
268
+ OwnedBy: "anturopic",
269
+ Permission: permission,
270
+ Root: "claude-instant-1",
271
+ Parent: nil,
272
+ },
273
+ {
274
+ Id: "claude-2",
275
+ Object: "model",
276
+ Created: 1677649963,
277
+ OwnedBy: "anturopic",
278
+ Permission: permission,
279
+ Root: "claude-2",
280
+ Parent: nil,
281
+ },
282
+ {
283
+ Id: "ERNIE-Bot",
284
+ Object: "model",
285
+ Created: 1677649963,
286
+ OwnedBy: "baidu",
287
+ Permission: permission,
288
+ Root: "ERNIE-Bot",
289
+ Parent: nil,
290
+ },
291
+ {
292
+ Id: "ERNIE-Bot-turbo",
293
+ Object: "model",
294
+ Created: 1677649963,
295
+ OwnedBy: "baidu",
296
+ Permission: permission,
297
+ Root: "ERNIE-Bot-turbo",
298
+ Parent: nil,
299
+ },
300
+ {
301
+ Id: "Embedding-V1",
302
+ Object: "model",
303
+ Created: 1677649963,
304
+ OwnedBy: "baidu",
305
+ Permission: permission,
306
+ Root: "Embedding-V1",
307
+ Parent: nil,
308
+ },
309
+ {
310
+ Id: "PaLM-2",
311
+ Object: "model",
312
+ Created: 1677649963,
313
+ OwnedBy: "google",
314
+ Permission: permission,
315
+ Root: "PaLM-2",
316
+ Parent: nil,
317
+ },
318
+ {
319
+ Id: "chatglm_pro",
320
+ Object: "model",
321
+ Created: 1677649963,
322
+ OwnedBy: "zhipu",
323
+ Permission: permission,
324
+ Root: "chatglm_pro",
325
+ Parent: nil,
326
+ },
327
+ {
328
+ Id: "chatglm_std",
329
+ Object: "model",
330
+ Created: 1677649963,
331
+ OwnedBy: "zhipu",
332
+ Permission: permission,
333
+ Root: "chatglm_std",
334
+ Parent: nil,
335
+ },
336
+ {
337
+ Id: "chatglm_lite",
338
+ Object: "model",
339
+ Created: 1677649963,
340
+ OwnedBy: "zhipu",
341
+ Permission: permission,
342
+ Root: "chatglm_lite",
343
+ Parent: nil,
344
+ },
345
+ {
346
+ Id: "qwen-v1",
347
+ Object: "model",
348
+ Created: 1677649963,
349
+ OwnedBy: "ali",
350
+ Permission: permission,
351
+ Root: "qwen-v1",
352
+ Parent: nil,
353
+ },
354
+ {
355
+ Id: "qwen-plus-v1",
356
+ Object: "model",
357
+ Created: 1677649963,
358
+ OwnedBy: "ali",
359
+ Permission: permission,
360
+ Root: "qwen-plus-v1",
361
+ Parent: nil,
362
+ },
363
+ {
364
+ Id: "SparkDesk",
365
+ Object: "model",
366
+ Created: 1677649963,
367
+ OwnedBy: "xunfei",
368
+ Permission: permission,
369
+ Root: "SparkDesk",
370
+ Parent: nil,
371
+ },
372
+ {
373
+ Id: "360GPT_S2_V9",
374
+ Object: "model",
375
+ Created: 1677649963,
376
+ OwnedBy: "360",
377
+ Permission: permission,
378
+ Root: "360GPT_S2_V9",
379
+ Parent: nil,
380
+ },
381
+ {
382
+ Id: "embedding-bert-512-v1",
383
+ Object: "model",
384
+ Created: 1677649963,
385
+ OwnedBy: "360",
386
+ Permission: permission,
387
+ Root: "embedding-bert-512-v1",
388
+ Parent: nil,
389
+ },
390
+ {
391
+ Id: "embedding_s1_v1",
392
+ Object: "model",
393
+ Created: 1677649963,
394
+ OwnedBy: "360",
395
+ Permission: permission,
396
+ Root: "embedding_s1_v1",
397
+ Parent: nil,
398
+ },
399
+ {
400
+ Id: "semantic_similarity_s1_v1",
401
+ Object: "model",
402
+ Created: 1677649963,
403
+ OwnedBy: "360",
404
+ Permission: permission,
405
+ Root: "semantic_similarity_s1_v1",
406
+ Parent: nil,
407
+ },
408
+ {
409
+ Id: "360GPT_S2_V9.4",
410
+ Object: "model",
411
+ Created: 1677649963,
412
+ OwnedBy: "360",
413
+ Permission: permission,
414
+ Root: "360GPT_S2_V9.4",
415
+ Parent: nil,
416
+ },
417
+ }
418
+ openAIModelsMap = make(map[string]OpenAIModels)
419
+ for _, model := range openAIModels {
420
+ openAIModelsMap[model.Id] = model
421
+ }
422
+ }
423
+
424
+ func ListModels(c *gin.Context) {
425
+ c.JSON(200, gin.H{
426
+ "object": "list",
427
+ "data": openAIModels,
428
+ })
429
+ }
430
+
431
+ func RetrieveModel(c *gin.Context) {
432
+ modelId := c.Param("model")
433
+ if model, ok := openAIModelsMap[modelId]; ok {
434
+ c.JSON(200, model)
435
+ } else {
436
+ openAIError := OpenAIError{
437
+ Message: fmt.Sprintf("The model '%s' does not exist", modelId),
438
+ Type: "invalid_request_error",
439
+ Param: "model",
440
+ Code: "model_not_found",
441
+ }
442
+ c.JSON(200, gin.H{
443
+ "error": openAIError,
444
+ })
445
+ }
446
+ }
controller/option.go ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "encoding/json"
5
+ "net/http"
6
+ "one-api/common"
7
+ "one-api/model"
8
+ "strings"
9
+
10
+ "github.com/gin-gonic/gin"
11
+ )
12
+
13
+ func GetOptions(c *gin.Context) {
14
+ var options []*model.Option
15
+ common.OptionMapRWMutex.Lock()
16
+ for k, v := range common.OptionMap {
17
+ if strings.HasSuffix(k, "Token") || strings.HasSuffix(k, "Secret") {
18
+ continue
19
+ }
20
+ options = append(options, &model.Option{
21
+ Key: k,
22
+ Value: common.Interface2String(v),
23
+ })
24
+ }
25
+ common.OptionMapRWMutex.Unlock()
26
+ c.JSON(http.StatusOK, gin.H{
27
+ "success": true,
28
+ "message": "",
29
+ "data": options,
30
+ })
31
+ return
32
+ }
33
+
34
+ func UpdateOption(c *gin.Context) {
35
+ var option model.Option
36
+ err := json.NewDecoder(c.Request.Body).Decode(&option)
37
+ if err != nil {
38
+ c.JSON(http.StatusBadRequest, gin.H{
39
+ "success": false,
40
+ "message": "无效的参数",
41
+ })
42
+ return
43
+ }
44
+ switch option.Key {
45
+ case "GitHubOAuthEnabled":
46
+ if option.Value == "true" && common.GitHubClientId == "" {
47
+ c.JSON(http.StatusOK, gin.H{
48
+ "success": false,
49
+ "message": "无法启用 GitHub OAuth,请先填入 GitHub Client ID 以及 GitHub Client Secret!",
50
+ })
51
+ return
52
+ }
53
+ case "EmailDomainRestrictionEnabled":
54
+ if option.Value == "true" && len(common.EmailDomainWhitelist) == 0 {
55
+ c.JSON(http.StatusOK, gin.H{
56
+ "success": false,
57
+ "message": "无法启用邮箱域名限制,请先填入限制的邮箱域名!",
58
+ })
59
+ return
60
+ }
61
+ case "WeChatAuthEnabled":
62
+ if option.Value == "true" && common.WeChatServerAddress == "" {
63
+ c.JSON(http.StatusOK, gin.H{
64
+ "success": false,
65
+ "message": "无法启用微信登录,请先填入微信登录相关配置信息!",
66
+ })
67
+ return
68
+ }
69
+ case "TurnstileCheckEnabled":
70
+ if option.Value == "true" && common.TurnstileSiteKey == "" {
71
+ c.JSON(http.StatusOK, gin.H{
72
+ "success": false,
73
+ "message": "无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!",
74
+ })
75
+ return
76
+ }
77
+ }
78
+ err = model.UpdateOption(option.Key, option.Value)
79
+ if err != nil {
80
+ c.JSON(http.StatusOK, gin.H{
81
+ "success": false,
82
+ "message": err.Error(),
83
+ })
84
+ return
85
+ }
86
+ c.JSON(http.StatusOK, gin.H{
87
+ "success": true,
88
+ "message": "",
89
+ })
90
+ return
91
+ }
controller/redemption.go ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+ "net/http"
6
+ "one-api/common"
7
+ "one-api/model"
8
+ "strconv"
9
+ )
10
+
11
+ func GetAllRedemptions(c *gin.Context) {
12
+ p, _ := strconv.Atoi(c.Query("p"))
13
+ if p < 0 {
14
+ p = 0
15
+ }
16
+ redemptions, err := model.GetAllRedemptions(p*common.ItemsPerPage, common.ItemsPerPage)
17
+ if err != nil {
18
+ c.JSON(http.StatusOK, gin.H{
19
+ "success": false,
20
+ "message": err.Error(),
21
+ })
22
+ return
23
+ }
24
+ c.JSON(http.StatusOK, gin.H{
25
+ "success": true,
26
+ "message": "",
27
+ "data": redemptions,
28
+ })
29
+ return
30
+ }
31
+
32
+ func SearchRedemptions(c *gin.Context) {
33
+ keyword := c.Query("keyword")
34
+ redemptions, err := model.SearchRedemptions(keyword)
35
+ if err != nil {
36
+ c.JSON(http.StatusOK, gin.H{
37
+ "success": false,
38
+ "message": err.Error(),
39
+ })
40
+ return
41
+ }
42
+ c.JSON(http.StatusOK, gin.H{
43
+ "success": true,
44
+ "message": "",
45
+ "data": redemptions,
46
+ })
47
+ return
48
+ }
49
+
50
+ func GetRedemption(c *gin.Context) {
51
+ id, err := strconv.Atoi(c.Param("id"))
52
+ if err != nil {
53
+ c.JSON(http.StatusOK, gin.H{
54
+ "success": false,
55
+ "message": err.Error(),
56
+ })
57
+ return
58
+ }
59
+ redemption, err := model.GetRedemptionById(id)
60
+ if err != nil {
61
+ c.JSON(http.StatusOK, gin.H{
62
+ "success": false,
63
+ "message": err.Error(),
64
+ })
65
+ return
66
+ }
67
+ c.JSON(http.StatusOK, gin.H{
68
+ "success": true,
69
+ "message": "",
70
+ "data": redemption,
71
+ })
72
+ return
73
+ }
74
+
75
+ func AddRedemption(c *gin.Context) {
76
+ redemption := model.Redemption{}
77
+ err := c.ShouldBindJSON(&redemption)
78
+ if err != nil {
79
+ c.JSON(http.StatusOK, gin.H{
80
+ "success": false,
81
+ "message": err.Error(),
82
+ })
83
+ return
84
+ }
85
+ if len(redemption.Name) == 0 || len(redemption.Name) > 20 {
86
+ c.JSON(http.StatusOK, gin.H{
87
+ "success": false,
88
+ "message": "兑换码名称长度必须在1-20之间",
89
+ })
90
+ return
91
+ }
92
+ if redemption.Count <= 0 {
93
+ c.JSON(http.StatusOK, gin.H{
94
+ "success": false,
95
+ "message": "兑换码个数必须大于0",
96
+ })
97
+ return
98
+ }
99
+ if redemption.Count > 100 {
100
+ c.JSON(http.StatusOK, gin.H{
101
+ "success": false,
102
+ "message": "一次兑换码批量生成的个数不能大于 100",
103
+ })
104
+ return
105
+ }
106
+ var keys []string
107
+ for i := 0; i < redemption.Count; i++ {
108
+ key := common.GetUUID()
109
+ cleanRedemption := model.Redemption{
110
+ UserId: c.GetInt("id"),
111
+ Name: redemption.Name,
112
+ Key: key,
113
+ CreatedTime: common.GetTimestamp(),
114
+ Quota: redemption.Quota,
115
+ }
116
+ err = cleanRedemption.Insert()
117
+ if err != nil {
118
+ c.JSON(http.StatusOK, gin.H{
119
+ "success": false,
120
+ "message": err.Error(),
121
+ "data": keys,
122
+ })
123
+ return
124
+ }
125
+ keys = append(keys, key)
126
+ }
127
+ c.JSON(http.StatusOK, gin.H{
128
+ "success": true,
129
+ "message": "",
130
+ "data": keys,
131
+ })
132
+ return
133
+ }
134
+
135
+ func DeleteRedemption(c *gin.Context) {
136
+ id, _ := strconv.Atoi(c.Param("id"))
137
+ err := model.DeleteRedemptionById(id)
138
+ if err != nil {
139
+ c.JSON(http.StatusOK, gin.H{
140
+ "success": false,
141
+ "message": err.Error(),
142
+ })
143
+ return
144
+ }
145
+ c.JSON(http.StatusOK, gin.H{
146
+ "success": true,
147
+ "message": "",
148
+ })
149
+ return
150
+ }
151
+
152
+ func UpdateRedemption(c *gin.Context) {
153
+ statusOnly := c.Query("status_only")
154
+ redemption := model.Redemption{}
155
+ err := c.ShouldBindJSON(&redemption)
156
+ if err != nil {
157
+ c.JSON(http.StatusOK, gin.H{
158
+ "success": false,
159
+ "message": err.Error(),
160
+ })
161
+ return
162
+ }
163
+ cleanRedemption, err := model.GetRedemptionById(redemption.Id)
164
+ if err != nil {
165
+ c.JSON(http.StatusOK, gin.H{
166
+ "success": false,
167
+ "message": err.Error(),
168
+ })
169
+ return
170
+ }
171
+ if statusOnly != "" {
172
+ cleanRedemption.Status = redemption.Status
173
+ } else {
174
+ // If you add more fields, please also update redemption.Update()
175
+ cleanRedemption.Name = redemption.Name
176
+ cleanRedemption.Quota = redemption.Quota
177
+ }
178
+ err = cleanRedemption.Update()
179
+ if err != nil {
180
+ c.JSON(http.StatusOK, gin.H{
181
+ "success": false,
182
+ "message": err.Error(),
183
+ })
184
+ return
185
+ }
186
+ c.JSON(http.StatusOK, gin.H{
187
+ "success": true,
188
+ "message": "",
189
+ "data": cleanRedemption,
190
+ })
191
+ return
192
+ }
controller/relay-ali.go ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bufio"
5
+ "encoding/json"
6
+ "github.com/gin-gonic/gin"
7
+ "io"
8
+ "net/http"
9
+ "one-api/common"
10
+ "strings"
11
+ )
12
+
13
+ // https://help.aliyun.com/document_detail/613695.html?spm=a2c4g.2399480.0.0.1adb778fAdzP9w#341800c0f8w0r
14
+
15
+ type AliMessage struct {
16
+ User string `json:"user"`
17
+ Bot string `json:"bot"`
18
+ }
19
+
20
+ type AliInput struct {
21
+ Prompt string `json:"prompt"`
22
+ History []AliMessage `json:"history"`
23
+ }
24
+
25
+ type AliParameters struct {
26
+ TopP float64 `json:"top_p,omitempty"`
27
+ TopK int `json:"top_k,omitempty"`
28
+ Seed uint64 `json:"seed,omitempty"`
29
+ EnableSearch bool `json:"enable_search,omitempty"`
30
+ }
31
+
32
+ type AliChatRequest struct {
33
+ Model string `json:"model"`
34
+ Input AliInput `json:"input"`
35
+ Parameters AliParameters `json:"parameters,omitempty"`
36
+ }
37
+
38
+ type AliError struct {
39
+ Code string `json:"code"`
40
+ Message string `json:"message"`
41
+ RequestId string `json:"request_id"`
42
+ }
43
+
44
+ type AliUsage struct {
45
+ InputTokens int `json:"input_tokens"`
46
+ OutputTokens int `json:"output_tokens"`
47
+ }
48
+
49
+ type AliOutput struct {
50
+ Text string `json:"text"`
51
+ FinishReason string `json:"finish_reason"`
52
+ }
53
+
54
+ type AliChatResponse struct {
55
+ Output AliOutput `json:"output"`
56
+ Usage AliUsage `json:"usage"`
57
+ AliError
58
+ }
59
+
60
+ func requestOpenAI2Ali(request GeneralOpenAIRequest) *AliChatRequest {
61
+ messages := make([]AliMessage, 0, len(request.Messages))
62
+ prompt := ""
63
+ for i := 0; i < len(request.Messages); i++ {
64
+ message := request.Messages[i]
65
+ if message.Role == "system" {
66
+ messages = append(messages, AliMessage{
67
+ User: message.Content,
68
+ Bot: "Okay",
69
+ })
70
+ continue
71
+ } else {
72
+ if i == len(request.Messages)-1 {
73
+ prompt = message.Content
74
+ break
75
+ }
76
+ messages = append(messages, AliMessage{
77
+ User: message.Content,
78
+ Bot: request.Messages[i+1].Content,
79
+ })
80
+ i++
81
+ }
82
+ }
83
+ return &AliChatRequest{
84
+ Model: request.Model,
85
+ Input: AliInput{
86
+ Prompt: prompt,
87
+ History: messages,
88
+ },
89
+ //Parameters: AliParameters{ // ChatGPT's parameters are not compatible with Ali's
90
+ // TopP: request.TopP,
91
+ // TopK: 50,
92
+ // //Seed: 0,
93
+ // //EnableSearch: false,
94
+ //},
95
+ }
96
+ }
97
+
98
+ func responseAli2OpenAI(response *AliChatResponse) *OpenAITextResponse {
99
+ choice := OpenAITextResponseChoice{
100
+ Index: 0,
101
+ Message: Message{
102
+ Role: "assistant",
103
+ Content: response.Output.Text,
104
+ },
105
+ FinishReason: response.Output.FinishReason,
106
+ }
107
+ fullTextResponse := OpenAITextResponse{
108
+ Id: response.RequestId,
109
+ Object: "chat.completion",
110
+ Created: common.GetTimestamp(),
111
+ Choices: []OpenAITextResponseChoice{choice},
112
+ Usage: Usage{
113
+ PromptTokens: response.Usage.InputTokens,
114
+ CompletionTokens: response.Usage.OutputTokens,
115
+ TotalTokens: response.Usage.InputTokens + response.Usage.OutputTokens,
116
+ },
117
+ }
118
+ return &fullTextResponse
119
+ }
120
+
121
+ func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *ChatCompletionsStreamResponse {
122
+ var choice ChatCompletionsStreamResponseChoice
123
+ choice.Delta.Content = aliResponse.Output.Text
124
+ if aliResponse.Output.FinishReason != "null" {
125
+ finishReason := aliResponse.Output.FinishReason
126
+ choice.FinishReason = &finishReason
127
+ }
128
+ response := ChatCompletionsStreamResponse{
129
+ Id: aliResponse.RequestId,
130
+ Object: "chat.completion.chunk",
131
+ Created: common.GetTimestamp(),
132
+ Model: "ernie-bot",
133
+ Choices: []ChatCompletionsStreamResponseChoice{choice},
134
+ }
135
+ return &response
136
+ }
137
+
138
+ func aliStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
139
+ var usage Usage
140
+ scanner := bufio.NewScanner(resp.Body)
141
+ scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
142
+ if atEOF && len(data) == 0 {
143
+ return 0, nil, nil
144
+ }
145
+ if i := strings.Index(string(data), "\n"); i >= 0 {
146
+ return i + 1, data[0:i], nil
147
+ }
148
+ if atEOF {
149
+ return len(data), data, nil
150
+ }
151
+ return 0, nil, nil
152
+ })
153
+ dataChan := make(chan string)
154
+ stopChan := make(chan bool)
155
+ go func() {
156
+ for scanner.Scan() {
157
+ data := scanner.Text()
158
+ if len(data) < 5 { // ignore blank line or wrong format
159
+ continue
160
+ }
161
+ if data[:5] != "data:" {
162
+ continue
163
+ }
164
+ data = data[5:]
165
+ dataChan <- data
166
+ }
167
+ stopChan <- true
168
+ }()
169
+ setEventStreamHeaders(c)
170
+ lastResponseText := ""
171
+ c.Stream(func(w io.Writer) bool {
172
+ select {
173
+ case data := <-dataChan:
174
+ var aliResponse AliChatResponse
175
+ err := json.Unmarshal([]byte(data), &aliResponse)
176
+ if err != nil {
177
+ common.SysError("error unmarshalling stream response: " + err.Error())
178
+ return true
179
+ }
180
+ if aliResponse.Usage.OutputTokens != 0 {
181
+ usage.PromptTokens = aliResponse.Usage.InputTokens
182
+ usage.CompletionTokens = aliResponse.Usage.OutputTokens
183
+ usage.TotalTokens = aliResponse.Usage.InputTokens + aliResponse.Usage.OutputTokens
184
+ }
185
+ response := streamResponseAli2OpenAI(&aliResponse)
186
+ response.Choices[0].Delta.Content = strings.TrimPrefix(response.Choices[0].Delta.Content, lastResponseText)
187
+ lastResponseText = aliResponse.Output.Text
188
+ jsonResponse, err := json.Marshal(response)
189
+ if err != nil {
190
+ common.SysError("error marshalling stream response: " + err.Error())
191
+ return true
192
+ }
193
+ c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)})
194
+ return true
195
+ case <-stopChan:
196
+ c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
197
+ return false
198
+ }
199
+ })
200
+ err := resp.Body.Close()
201
+ if err != nil {
202
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
203
+ }
204
+ return nil, &usage
205
+ }
206
+
207
+ func aliHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
208
+ var aliResponse AliChatResponse
209
+ responseBody, err := io.ReadAll(resp.Body)
210
+ if err != nil {
211
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
212
+ }
213
+ err = resp.Body.Close()
214
+ if err != nil {
215
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
216
+ }
217
+ err = json.Unmarshal(responseBody, &aliResponse)
218
+ if err != nil {
219
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
220
+ }
221
+ if aliResponse.Code != "" {
222
+ return &OpenAIErrorWithStatusCode{
223
+ OpenAIError: OpenAIError{
224
+ Message: aliResponse.Message,
225
+ Type: aliResponse.Code,
226
+ Param: aliResponse.RequestId,
227
+ Code: aliResponse.Code,
228
+ },
229
+ StatusCode: resp.StatusCode,
230
+ }, nil
231
+ }
232
+ fullTextResponse := responseAli2OpenAI(&aliResponse)
233
+ jsonResponse, err := json.Marshal(fullTextResponse)
234
+ if err != nil {
235
+ return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
236
+ }
237
+ c.Writer.Header().Set("Content-Type", "application/json")
238
+ c.Writer.WriteHeader(resp.StatusCode)
239
+ _, err = c.Writer.Write(jsonResponse)
240
+ return nil, &fullTextResponse.Usage
241
+ }
controller/relay-audio.go ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "net/http"
9
+ "one-api/common"
10
+ "one-api/model"
11
+
12
+ "github.com/gin-gonic/gin"
13
+ )
14
+
15
+ func relayAudioHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
16
+ audioModel := "whisper-1"
17
+
18
+ tokenId := c.GetInt("token_id")
19
+ channelType := c.GetInt("channel")
20
+ userId := c.GetInt("id")
21
+ group := c.GetString("group")
22
+
23
+ preConsumedTokens := common.PreConsumedQuota
24
+ modelRatio := common.GetModelRatio(audioModel)
25
+ groupRatio := common.GetGroupRatio(group)
26
+ ratio := modelRatio * groupRatio
27
+ preConsumedQuota := int(float64(preConsumedTokens) * ratio)
28
+ userQuota, err := model.CacheGetUserQuota(userId)
29
+ if err != nil {
30
+ return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError)
31
+ }
32
+ err = model.CacheDecreaseUserQuota(userId, preConsumedQuota)
33
+ if err != nil {
34
+ return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError)
35
+ }
36
+ if userQuota > 100*preConsumedQuota {
37
+ // in this case, we do not pre-consume quota
38
+ // because the user has enough quota
39
+ preConsumedQuota = 0
40
+ }
41
+ if preConsumedQuota > 0 {
42
+ err := model.PreConsumeTokenQuota(tokenId, preConsumedQuota)
43
+ if err != nil {
44
+ return errorWrapper(err, "pre_consume_token_quota_failed", http.StatusForbidden)
45
+ }
46
+ }
47
+
48
+ // map model name
49
+ modelMapping := c.GetString("model_mapping")
50
+ if modelMapping != "" {
51
+ modelMap := make(map[string]string)
52
+ err := json.Unmarshal([]byte(modelMapping), &modelMap)
53
+ if err != nil {
54
+ return errorWrapper(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError)
55
+ }
56
+ if modelMap[audioModel] != "" {
57
+ audioModel = modelMap[audioModel]
58
+ }
59
+ }
60
+
61
+ baseURL := common.ChannelBaseURLs[channelType]
62
+ requestURL := c.Request.URL.String()
63
+
64
+ if c.GetString("base_url") != "" {
65
+ baseURL = c.GetString("base_url")
66
+ }
67
+
68
+ fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL)
69
+ requestBody := c.Request.Body
70
+
71
+ req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody)
72
+ if err != nil {
73
+ return errorWrapper(err, "new_request_failed", http.StatusInternalServerError)
74
+ }
75
+ req.Header.Set("Authorization", c.Request.Header.Get("Authorization"))
76
+ req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
77
+ req.Header.Set("Accept", c.Request.Header.Get("Accept"))
78
+
79
+ resp, err := httpClient.Do(req)
80
+ if err != nil {
81
+ return errorWrapper(err, "do_request_failed", http.StatusInternalServerError)
82
+ }
83
+
84
+ err = req.Body.Close()
85
+ if err != nil {
86
+ return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError)
87
+ }
88
+ err = c.Request.Body.Close()
89
+ if err != nil {
90
+ return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError)
91
+ }
92
+ var audioResponse AudioResponse
93
+
94
+ defer func() {
95
+ go func() {
96
+ quota := countTokenText(audioResponse.Text, audioModel)
97
+ quotaDelta := quota - preConsumedQuota
98
+ err := model.PostConsumeTokenQuota(tokenId, quotaDelta)
99
+ if err != nil {
100
+ common.SysError("error consuming token remain quota: " + err.Error())
101
+ }
102
+ err = model.CacheUpdateUserQuota(userId)
103
+ if err != nil {
104
+ common.SysError("error update user quota cache: " + err.Error())
105
+ }
106
+ if quota != 0 {
107
+ tokenName := c.GetString("token_name")
108
+ logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
109
+ model.RecordConsumeLog(userId, 0, 0, audioModel, tokenName, quota, logContent)
110
+ model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
111
+ channelId := c.GetInt("channel_id")
112
+ model.UpdateChannelUsedQuota(channelId, quota)
113
+ }
114
+ }()
115
+ }()
116
+
117
+ responseBody, err := io.ReadAll(resp.Body)
118
+
119
+ if err != nil {
120
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
121
+ }
122
+ err = resp.Body.Close()
123
+ if err != nil {
124
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError)
125
+ }
126
+ err = json.Unmarshal(responseBody, &audioResponse)
127
+ if err != nil {
128
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError)
129
+ }
130
+
131
+ resp.Body = io.NopCloser(bytes.NewBuffer(responseBody))
132
+
133
+ for k, v := range resp.Header {
134
+ c.Writer.Header().Set(k, v[0])
135
+ }
136
+ c.Writer.WriteHeader(resp.StatusCode)
137
+
138
+ _, err = io.Copy(c.Writer, resp.Body)
139
+ if err != nil {
140
+ return errorWrapper(err, "copy_response_body_failed", http.StatusInternalServerError)
141
+ }
142
+ err = resp.Body.Close()
143
+ if err != nil {
144
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError)
145
+ }
146
+ return nil
147
+ }
controller/relay-baidu.go ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bufio"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "github.com/gin-gonic/gin"
9
+ "io"
10
+ "net/http"
11
+ "one-api/common"
12
+ "strings"
13
+ "sync"
14
+ "time"
15
+ )
16
+
17
+ // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/flfmc9do2
18
+
19
+ type BaiduTokenResponse struct {
20
+ ExpiresIn int `json:"expires_in"`
21
+ AccessToken string `json:"access_token"`
22
+ }
23
+
24
+ type BaiduMessage struct {
25
+ Role string `json:"role"`
26
+ Content string `json:"content"`
27
+ }
28
+
29
+ type BaiduChatRequest struct {
30
+ Messages []BaiduMessage `json:"messages"`
31
+ Stream bool `json:"stream"`
32
+ UserId string `json:"user_id,omitempty"`
33
+ }
34
+
35
+ type BaiduError struct {
36
+ ErrorCode int `json:"error_code"`
37
+ ErrorMsg string `json:"error_msg"`
38
+ }
39
+
40
+ type BaiduChatResponse struct {
41
+ Id string `json:"id"`
42
+ Object string `json:"object"`
43
+ Created int64 `json:"created"`
44
+ Result string `json:"result"`
45
+ IsTruncated bool `json:"is_truncated"`
46
+ NeedClearHistory bool `json:"need_clear_history"`
47
+ Usage Usage `json:"usage"`
48
+ BaiduError
49
+ }
50
+
51
+ type BaiduChatStreamResponse struct {
52
+ BaiduChatResponse
53
+ SentenceId int `json:"sentence_id"`
54
+ IsEnd bool `json:"is_end"`
55
+ }
56
+
57
+ type BaiduEmbeddingRequest struct {
58
+ Input []string `json:"input"`
59
+ }
60
+
61
+ type BaiduEmbeddingData struct {
62
+ Object string `json:"object"`
63
+ Embedding []float64 `json:"embedding"`
64
+ Index int `json:"index"`
65
+ }
66
+
67
+ type BaiduEmbeddingResponse struct {
68
+ Id string `json:"id"`
69
+ Object string `json:"object"`
70
+ Created int64 `json:"created"`
71
+ Data []BaiduEmbeddingData `json:"data"`
72
+ Usage Usage `json:"usage"`
73
+ BaiduError
74
+ }
75
+
76
+ type BaiduAccessToken struct {
77
+ AccessToken string `json:"access_token"`
78
+ Error string `json:"error,omitempty"`
79
+ ErrorDescription string `json:"error_description,omitempty"`
80
+ ExpiresIn int64 `json:"expires_in,omitempty"`
81
+ ExpiresAt time.Time `json:"-"`
82
+ }
83
+
84
+ var baiduTokenStore sync.Map
85
+
86
+ func requestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduChatRequest {
87
+ messages := make([]BaiduMessage, 0, len(request.Messages))
88
+ for _, message := range request.Messages {
89
+ if message.Role == "system" {
90
+ messages = append(messages, BaiduMessage{
91
+ Role: "user",
92
+ Content: message.Content,
93
+ })
94
+ messages = append(messages, BaiduMessage{
95
+ Role: "assistant",
96
+ Content: "Okay",
97
+ })
98
+ } else {
99
+ messages = append(messages, BaiduMessage{
100
+ Role: message.Role,
101
+ Content: message.Content,
102
+ })
103
+ }
104
+ }
105
+ return &BaiduChatRequest{
106
+ Messages: messages,
107
+ Stream: request.Stream,
108
+ }
109
+ }
110
+
111
+ func responseBaidu2OpenAI(response *BaiduChatResponse) *OpenAITextResponse {
112
+ choice := OpenAITextResponseChoice{
113
+ Index: 0,
114
+ Message: Message{
115
+ Role: "assistant",
116
+ Content: response.Result,
117
+ },
118
+ FinishReason: "stop",
119
+ }
120
+ fullTextResponse := OpenAITextResponse{
121
+ Id: response.Id,
122
+ Object: "chat.completion",
123
+ Created: response.Created,
124
+ Choices: []OpenAITextResponseChoice{choice},
125
+ Usage: response.Usage,
126
+ }
127
+ return &fullTextResponse
128
+ }
129
+
130
+ func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *ChatCompletionsStreamResponse {
131
+ var choice ChatCompletionsStreamResponseChoice
132
+ choice.Delta.Content = baiduResponse.Result
133
+ if baiduResponse.IsEnd {
134
+ choice.FinishReason = &stopFinishReason
135
+ }
136
+ response := ChatCompletionsStreamResponse{
137
+ Id: baiduResponse.Id,
138
+ Object: "chat.completion.chunk",
139
+ Created: baiduResponse.Created,
140
+ Model: "ernie-bot",
141
+ Choices: []ChatCompletionsStreamResponseChoice{choice},
142
+ }
143
+ return &response
144
+ }
145
+
146
+ func embeddingRequestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduEmbeddingRequest {
147
+ baiduEmbeddingRequest := BaiduEmbeddingRequest{
148
+ Input: nil,
149
+ }
150
+ switch request.Input.(type) {
151
+ case string:
152
+ baiduEmbeddingRequest.Input = []string{request.Input.(string)}
153
+ case []any:
154
+ for _, item := range request.Input.([]any) {
155
+ if str, ok := item.(string); ok {
156
+ baiduEmbeddingRequest.Input = append(baiduEmbeddingRequest.Input, str)
157
+ }
158
+ }
159
+ }
160
+ return &baiduEmbeddingRequest
161
+ }
162
+
163
+ func embeddingResponseBaidu2OpenAI(response *BaiduEmbeddingResponse) *OpenAIEmbeddingResponse {
164
+ openAIEmbeddingResponse := OpenAIEmbeddingResponse{
165
+ Object: "list",
166
+ Data: make([]OpenAIEmbeddingResponseItem, 0, len(response.Data)),
167
+ Model: "baidu-embedding",
168
+ Usage: response.Usage,
169
+ }
170
+ for _, item := range response.Data {
171
+ openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, OpenAIEmbeddingResponseItem{
172
+ Object: item.Object,
173
+ Index: item.Index,
174
+ Embedding: item.Embedding,
175
+ })
176
+ }
177
+ return &openAIEmbeddingResponse
178
+ }
179
+
180
+ func baiduStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
181
+ var usage Usage
182
+ scanner := bufio.NewScanner(resp.Body)
183
+ scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
184
+ if atEOF && len(data) == 0 {
185
+ return 0, nil, nil
186
+ }
187
+ if i := strings.Index(string(data), "\n"); i >= 0 {
188
+ return i + 1, data[0:i], nil
189
+ }
190
+ if atEOF {
191
+ return len(data), data, nil
192
+ }
193
+ return 0, nil, nil
194
+ })
195
+ dataChan := make(chan string)
196
+ stopChan := make(chan bool)
197
+ go func() {
198
+ for scanner.Scan() {
199
+ data := scanner.Text()
200
+ if len(data) < 6 { // ignore blank line or wrong format
201
+ continue
202
+ }
203
+ data = data[6:]
204
+ dataChan <- data
205
+ }
206
+ stopChan <- true
207
+ }()
208
+ setEventStreamHeaders(c)
209
+ c.Stream(func(w io.Writer) bool {
210
+ select {
211
+ case data := <-dataChan:
212
+ var baiduResponse BaiduChatStreamResponse
213
+ err := json.Unmarshal([]byte(data), &baiduResponse)
214
+ if err != nil {
215
+ common.SysError("error unmarshalling stream response: " + err.Error())
216
+ return true
217
+ }
218
+ if baiduResponse.Usage.TotalTokens != 0 {
219
+ usage.TotalTokens = baiduResponse.Usage.TotalTokens
220
+ usage.PromptTokens = baiduResponse.Usage.PromptTokens
221
+ usage.CompletionTokens = baiduResponse.Usage.TotalTokens - baiduResponse.Usage.PromptTokens
222
+ }
223
+ response := streamResponseBaidu2OpenAI(&baiduResponse)
224
+ jsonResponse, err := json.Marshal(response)
225
+ if err != nil {
226
+ common.SysError("error marshalling stream response: " + err.Error())
227
+ return true
228
+ }
229
+ c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)})
230
+ return true
231
+ case <-stopChan:
232
+ c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
233
+ return false
234
+ }
235
+ })
236
+ err := resp.Body.Close()
237
+ if err != nil {
238
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
239
+ }
240
+ return nil, &usage
241
+ }
242
+
243
+ func baiduHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
244
+ var baiduResponse BaiduChatResponse
245
+ responseBody, err := io.ReadAll(resp.Body)
246
+ if err != nil {
247
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
248
+ }
249
+ err = resp.Body.Close()
250
+ if err != nil {
251
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
252
+ }
253
+ err = json.Unmarshal(responseBody, &baiduResponse)
254
+ if err != nil {
255
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
256
+ }
257
+ if baiduResponse.ErrorMsg != "" {
258
+ return &OpenAIErrorWithStatusCode{
259
+ OpenAIError: OpenAIError{
260
+ Message: baiduResponse.ErrorMsg,
261
+ Type: "baidu_error",
262
+ Param: "",
263
+ Code: baiduResponse.ErrorCode,
264
+ },
265
+ StatusCode: resp.StatusCode,
266
+ }, nil
267
+ }
268
+ fullTextResponse := responseBaidu2OpenAI(&baiduResponse)
269
+ jsonResponse, err := json.Marshal(fullTextResponse)
270
+ if err != nil {
271
+ return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
272
+ }
273
+ c.Writer.Header().Set("Content-Type", "application/json")
274
+ c.Writer.WriteHeader(resp.StatusCode)
275
+ _, err = c.Writer.Write(jsonResponse)
276
+ return nil, &fullTextResponse.Usage
277
+ }
278
+
279
+ func baiduEmbeddingHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
280
+ var baiduResponse BaiduEmbeddingResponse
281
+ responseBody, err := io.ReadAll(resp.Body)
282
+ if err != nil {
283
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
284
+ }
285
+ err = resp.Body.Close()
286
+ if err != nil {
287
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
288
+ }
289
+ err = json.Unmarshal(responseBody, &baiduResponse)
290
+ if err != nil {
291
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
292
+ }
293
+ if baiduResponse.ErrorMsg != "" {
294
+ return &OpenAIErrorWithStatusCode{
295
+ OpenAIError: OpenAIError{
296
+ Message: baiduResponse.ErrorMsg,
297
+ Type: "baidu_error",
298
+ Param: "",
299
+ Code: baiduResponse.ErrorCode,
300
+ },
301
+ StatusCode: resp.StatusCode,
302
+ }, nil
303
+ }
304
+ fullTextResponse := embeddingResponseBaidu2OpenAI(&baiduResponse)
305
+ jsonResponse, err := json.Marshal(fullTextResponse)
306
+ if err != nil {
307
+ return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
308
+ }
309
+ c.Writer.Header().Set("Content-Type", "application/json")
310
+ c.Writer.WriteHeader(resp.StatusCode)
311
+ _, err = c.Writer.Write(jsonResponse)
312
+ return nil, &fullTextResponse.Usage
313
+ }
314
+
315
+ func getBaiduAccessToken(apiKey string) (string, error) {
316
+ if val, ok := baiduTokenStore.Load(apiKey); ok {
317
+ var accessToken BaiduAccessToken
318
+ if accessToken, ok = val.(BaiduAccessToken); ok {
319
+ // soon this will expire
320
+ if time.Now().Add(time.Hour).After(accessToken.ExpiresAt) {
321
+ go func() {
322
+ _, _ = getBaiduAccessTokenHelper(apiKey)
323
+ }()
324
+ }
325
+ return accessToken.AccessToken, nil
326
+ }
327
+ }
328
+ accessToken, err := getBaiduAccessTokenHelper(apiKey)
329
+ if err != nil {
330
+ return "", err
331
+ }
332
+ if accessToken == nil {
333
+ return "", errors.New("getBaiduAccessToken return a nil token")
334
+ }
335
+ return (*accessToken).AccessToken, nil
336
+ }
337
+
338
+ func getBaiduAccessTokenHelper(apiKey string) (*BaiduAccessToken, error) {
339
+ parts := strings.Split(apiKey, "|")
340
+ if len(parts) != 2 {
341
+ return nil, errors.New("invalid baidu apikey")
342
+ }
343
+ req, err := http.NewRequest("POST", fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s",
344
+ parts[0], parts[1]), nil)
345
+ if err != nil {
346
+ return nil, err
347
+ }
348
+ req.Header.Add("Content-Type", "application/json")
349
+ req.Header.Add("Accept", "application/json")
350
+ res, err := impatientHTTPClient.Do(req)
351
+ if err != nil {
352
+ return nil, err
353
+ }
354
+ defer res.Body.Close()
355
+
356
+ var accessToken BaiduAccessToken
357
+ err = json.NewDecoder(res.Body).Decode(&accessToken)
358
+ if err != nil {
359
+ return nil, err
360
+ }
361
+ if accessToken.Error != "" {
362
+ return nil, errors.New(accessToken.Error + ": " + accessToken.ErrorDescription)
363
+ }
364
+ if accessToken.AccessToken == "" {
365
+ return nil, errors.New("getBaiduAccessTokenHelper get empty access token")
366
+ }
367
+ accessToken.ExpiresAt = time.Now().Add(time.Duration(accessToken.ExpiresIn) * time.Second)
368
+ baiduTokenStore.Store(apiKey, accessToken)
369
+ return &accessToken, nil
370
+ }
controller/relay-claude.go ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bufio"
5
+ "encoding/json"
6
+ "fmt"
7
+ "github.com/gin-gonic/gin"
8
+ "io"
9
+ "net/http"
10
+ "one-api/common"
11
+ "strings"
12
+ )
13
+
14
+ type ClaudeMetadata struct {
15
+ UserId string `json:"user_id"`
16
+ }
17
+
18
+ type ClaudeRequest struct {
19
+ Model string `json:"model"`
20
+ Prompt string `json:"prompt"`
21
+ MaxTokensToSample int `json:"max_tokens_to_sample"`
22
+ StopSequences []string `json:"stop_sequences,omitempty"`
23
+ Temperature float64 `json:"temperature,omitempty"`
24
+ TopP float64 `json:"top_p,omitempty"`
25
+ TopK int `json:"top_k,omitempty"`
26
+ //ClaudeMetadata `json:"metadata,omitempty"`
27
+ Stream bool `json:"stream,omitempty"`
28
+ }
29
+
30
+ type ClaudeError struct {
31
+ Type string `json:"type"`
32
+ Message string `json:"message"`
33
+ }
34
+
35
+ type ClaudeResponse struct {
36
+ Completion string `json:"completion"`
37
+ StopReason string `json:"stop_reason"`
38
+ Model string `json:"model"`
39
+ Error ClaudeError `json:"error"`
40
+ }
41
+
42
+ func stopReasonClaude2OpenAI(reason string) string {
43
+ switch reason {
44
+ case "stop_sequence":
45
+ return "stop"
46
+ case "max_tokens":
47
+ return "length"
48
+ default:
49
+ return reason
50
+ }
51
+ }
52
+
53
+ func requestOpenAI2Claude(textRequest GeneralOpenAIRequest) *ClaudeRequest {
54
+ claudeRequest := ClaudeRequest{
55
+ Model: textRequest.Model,
56
+ Prompt: "",
57
+ MaxTokensToSample: textRequest.MaxTokens,
58
+ StopSequences: nil,
59
+ Temperature: textRequest.Temperature,
60
+ TopP: textRequest.TopP,
61
+ Stream: textRequest.Stream,
62
+ }
63
+ if claudeRequest.MaxTokensToSample == 0 {
64
+ claudeRequest.MaxTokensToSample = 1000000
65
+ }
66
+ prompt := ""
67
+ for _, message := range textRequest.Messages {
68
+ if message.Role == "user" {
69
+ prompt += fmt.Sprintf("\n\nHuman: %s", message.Content)
70
+ } else if message.Role == "assistant" {
71
+ prompt += fmt.Sprintf("\n\nAssistant: %s", message.Content)
72
+ } else if message.Role == "system" {
73
+ prompt += fmt.Sprintf("\n\nSystem: %s", message.Content)
74
+ }
75
+ }
76
+ prompt += "\n\nAssistant:"
77
+ claudeRequest.Prompt = prompt
78
+ return &claudeRequest
79
+ }
80
+
81
+ func streamResponseClaude2OpenAI(claudeResponse *ClaudeResponse) *ChatCompletionsStreamResponse {
82
+ var choice ChatCompletionsStreamResponseChoice
83
+ choice.Delta.Content = claudeResponse.Completion
84
+ finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason)
85
+ if finishReason != "null" {
86
+ choice.FinishReason = &finishReason
87
+ }
88
+ var response ChatCompletionsStreamResponse
89
+ response.Object = "chat.completion.chunk"
90
+ response.Model = claudeResponse.Model
91
+ response.Choices = []ChatCompletionsStreamResponseChoice{choice}
92
+ return &response
93
+ }
94
+
95
+ func responseClaude2OpenAI(claudeResponse *ClaudeResponse) *OpenAITextResponse {
96
+ choice := OpenAITextResponseChoice{
97
+ Index: 0,
98
+ Message: Message{
99
+ Role: "assistant",
100
+ Content: strings.TrimPrefix(claudeResponse.Completion, " "),
101
+ Name: nil,
102
+ },
103
+ FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
104
+ }
105
+ fullTextResponse := OpenAITextResponse{
106
+ Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
107
+ Object: "chat.completion",
108
+ Created: common.GetTimestamp(),
109
+ Choices: []OpenAITextResponseChoice{choice},
110
+ }
111
+ return &fullTextResponse
112
+ }
113
+
114
+ func claudeStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, string) {
115
+ responseText := ""
116
+ responseId := fmt.Sprintf("chatcmpl-%s", common.GetUUID())
117
+ createdTime := common.GetTimestamp()
118
+ scanner := bufio.NewScanner(resp.Body)
119
+ scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
120
+ if atEOF && len(data) == 0 {
121
+ return 0, nil, nil
122
+ }
123
+ if i := strings.Index(string(data), "\r\n\r\n"); i >= 0 {
124
+ return i + 4, data[0:i], nil
125
+ }
126
+ if atEOF {
127
+ return len(data), data, nil
128
+ }
129
+ return 0, nil, nil
130
+ })
131
+ dataChan := make(chan string)
132
+ stopChan := make(chan bool)
133
+ go func() {
134
+ for scanner.Scan() {
135
+ data := scanner.Text()
136
+ if !strings.HasPrefix(data, "event: completion") {
137
+ continue
138
+ }
139
+ data = strings.TrimPrefix(data, "event: completion\r\ndata: ")
140
+ dataChan <- data
141
+ }
142
+ stopChan <- true
143
+ }()
144
+ setEventStreamHeaders(c)
145
+ c.Stream(func(w io.Writer) bool {
146
+ select {
147
+ case data := <-dataChan:
148
+ // some implementations may add \r at the end of data
149
+ data = strings.TrimSuffix(data, "\r")
150
+ var claudeResponse ClaudeResponse
151
+ err := json.Unmarshal([]byte(data), &claudeResponse)
152
+ if err != nil {
153
+ common.SysError("error unmarshalling stream response: " + err.Error())
154
+ return true
155
+ }
156
+ responseText += claudeResponse.Completion
157
+ response := streamResponseClaude2OpenAI(&claudeResponse)
158
+ response.Id = responseId
159
+ response.Created = createdTime
160
+ jsonStr, err := json.Marshal(response)
161
+ if err != nil {
162
+ common.SysError("error marshalling stream response: " + err.Error())
163
+ return true
164
+ }
165
+ c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonStr)})
166
+ return true
167
+ case <-stopChan:
168
+ c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
169
+ return false
170
+ }
171
+ })
172
+ err := resp.Body.Close()
173
+ if err != nil {
174
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), ""
175
+ }
176
+ return nil, responseText
177
+ }
178
+
179
+ func claudeHandler(c *gin.Context, resp *http.Response, promptTokens int, model string) (*OpenAIErrorWithStatusCode, *Usage) {
180
+ responseBody, err := io.ReadAll(resp.Body)
181
+ if err != nil {
182
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
183
+ }
184
+ err = resp.Body.Close()
185
+ if err != nil {
186
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
187
+ }
188
+ var claudeResponse ClaudeResponse
189
+ err = json.Unmarshal(responseBody, &claudeResponse)
190
+ if err != nil {
191
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
192
+ }
193
+ if claudeResponse.Error.Type != "" {
194
+ return &OpenAIErrorWithStatusCode{
195
+ OpenAIError: OpenAIError{
196
+ Message: claudeResponse.Error.Message,
197
+ Type: claudeResponse.Error.Type,
198
+ Param: "",
199
+ Code: claudeResponse.Error.Type,
200
+ },
201
+ StatusCode: resp.StatusCode,
202
+ }, nil
203
+ }
204
+ fullTextResponse := responseClaude2OpenAI(&claudeResponse)
205
+ completionTokens := countTokenText(claudeResponse.Completion, model)
206
+ usage := Usage{
207
+ PromptTokens: promptTokens,
208
+ CompletionTokens: completionTokens,
209
+ TotalTokens: promptTokens + completionTokens,
210
+ }
211
+ fullTextResponse.Usage = usage
212
+ jsonResponse, err := json.Marshal(fullTextResponse)
213
+ if err != nil {
214
+ return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
215
+ }
216
+ c.Writer.Header().Set("Content-Type", "application/json")
217
+ c.Writer.WriteHeader(resp.StatusCode)
218
+ _, err = c.Writer.Write(jsonResponse)
219
+ return nil, &usage
220
+ }
controller/relay-image.go ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "io"
9
+ "net/http"
10
+ "one-api/common"
11
+ "one-api/model"
12
+
13
+ "github.com/gin-gonic/gin"
14
+ )
15
+
16
+ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
17
+ imageModel := "dall-e"
18
+
19
+ tokenId := c.GetInt("token_id")
20
+ channelType := c.GetInt("channel")
21
+ userId := c.GetInt("id")
22
+ consumeQuota := c.GetBool("consume_quota")
23
+ group := c.GetString("group")
24
+
25
+ var imageRequest ImageRequest
26
+ if consumeQuota {
27
+ err := common.UnmarshalBodyReusable(c, &imageRequest)
28
+ if err != nil {
29
+ return errorWrapper(err, "bind_request_body_failed", http.StatusBadRequest)
30
+ }
31
+ }
32
+
33
+ // Prompt validation
34
+ if imageRequest.Prompt == "" {
35
+ return errorWrapper(errors.New("prompt is required"), "required_field_missing", http.StatusBadRequest)
36
+ }
37
+
38
+ // Not "256x256", "512x512", or "1024x1024"
39
+ if imageRequest.Size != "" && imageRequest.Size != "256x256" && imageRequest.Size != "512x512" && imageRequest.Size != "1024x1024" {
40
+ return errorWrapper(errors.New("size must be one of 256x256, 512x512, or 1024x1024"), "invalid_field_value", http.StatusBadRequest)
41
+ }
42
+
43
+ // N should between 1 and 10
44
+ if imageRequest.N != 0 && (imageRequest.N < 1 || imageRequest.N > 10) {
45
+ return errorWrapper(errors.New("n must be between 1 and 10"), "invalid_field_value", http.StatusBadRequest)
46
+ }
47
+
48
+ // map model name
49
+ modelMapping := c.GetString("model_mapping")
50
+ isModelMapped := false
51
+ if modelMapping != "" {
52
+ modelMap := make(map[string]string)
53
+ err := json.Unmarshal([]byte(modelMapping), &modelMap)
54
+ if err != nil {
55
+ return errorWrapper(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError)
56
+ }
57
+ if modelMap[imageModel] != "" {
58
+ imageModel = modelMap[imageModel]
59
+ isModelMapped = true
60
+ }
61
+ }
62
+
63
+ baseURL := common.ChannelBaseURLs[channelType]
64
+ requestURL := c.Request.URL.String()
65
+
66
+ if c.GetString("base_url") != "" {
67
+ baseURL = c.GetString("base_url")
68
+ }
69
+
70
+ fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL)
71
+
72
+ var requestBody io.Reader
73
+ if isModelMapped {
74
+ jsonStr, err := json.Marshal(imageRequest)
75
+ if err != nil {
76
+ return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError)
77
+ }
78
+ requestBody = bytes.NewBuffer(jsonStr)
79
+ } else {
80
+ requestBody = c.Request.Body
81
+ }
82
+
83
+ modelRatio := common.GetModelRatio(imageModel)
84
+ groupRatio := common.GetGroupRatio(group)
85
+ ratio := modelRatio * groupRatio
86
+ userQuota, err := model.CacheGetUserQuota(userId)
87
+
88
+ sizeRatio := 1.0
89
+ // Size
90
+ if imageRequest.Size == "256x256" {
91
+ sizeRatio = 1
92
+ } else if imageRequest.Size == "512x512" {
93
+ sizeRatio = 1.125
94
+ } else if imageRequest.Size == "1024x1024" {
95
+ sizeRatio = 1.25
96
+ }
97
+ quota := int(ratio*sizeRatio*1000) * imageRequest.N
98
+
99
+ if consumeQuota && userQuota-quota < 0 {
100
+ return errorWrapper(err, "insufficient_user_quota", http.StatusForbidden)
101
+ }
102
+
103
+ req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody)
104
+ if err != nil {
105
+ return errorWrapper(err, "new_request_failed", http.StatusInternalServerError)
106
+ }
107
+ req.Header.Set("Authorization", c.Request.Header.Get("Authorization"))
108
+
109
+ req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
110
+ req.Header.Set("Accept", c.Request.Header.Get("Accept"))
111
+
112
+ resp, err := httpClient.Do(req)
113
+ if err != nil {
114
+ return errorWrapper(err, "do_request_failed", http.StatusInternalServerError)
115
+ }
116
+
117
+ err = req.Body.Close()
118
+ if err != nil {
119
+ return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError)
120
+ }
121
+ err = c.Request.Body.Close()
122
+ if err != nil {
123
+ return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError)
124
+ }
125
+ var textResponse ImageResponse
126
+
127
+ defer func() {
128
+ if consumeQuota {
129
+ err := model.PostConsumeTokenQuota(tokenId, quota)
130
+ if err != nil {
131
+ common.SysError("error consuming token remain quota: " + err.Error())
132
+ }
133
+ err = model.CacheUpdateUserQuota(userId)
134
+ if err != nil {
135
+ common.SysError("error update user quota cache: " + err.Error())
136
+ }
137
+ if quota != 0 {
138
+ tokenName := c.GetString("token_name")
139
+ logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
140
+ model.RecordConsumeLog(userId, 0, 0, imageModel, tokenName, quota, logContent)
141
+ model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
142
+ channelId := c.GetInt("channel_id")
143
+ model.UpdateChannelUsedQuota(channelId, quota)
144
+ }
145
+ }
146
+ }()
147
+
148
+ if consumeQuota {
149
+ responseBody, err := io.ReadAll(resp.Body)
150
+
151
+ if err != nil {
152
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
153
+ }
154
+ err = resp.Body.Close()
155
+ if err != nil {
156
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError)
157
+ }
158
+ err = json.Unmarshal(responseBody, &textResponse)
159
+ if err != nil {
160
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError)
161
+ }
162
+
163
+ resp.Body = io.NopCloser(bytes.NewBuffer(responseBody))
164
+ }
165
+
166
+ for k, v := range resp.Header {
167
+ c.Writer.Header().Set(k, v[0])
168
+ }
169
+ c.Writer.WriteHeader(resp.StatusCode)
170
+
171
+ _, err = io.Copy(c.Writer, resp.Body)
172
+ if err != nil {
173
+ return errorWrapper(err, "copy_response_body_failed", http.StatusInternalServerError)
174
+ }
175
+ err = resp.Body.Close()
176
+ if err != nil {
177
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError)
178
+ }
179
+ return nil
180
+ }
controller/relay-openai.go ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bufio"
5
+ "bytes"
6
+ "encoding/json"
7
+ "github.com/gin-gonic/gin"
8
+ "io"
9
+ "net/http"
10
+ "one-api/common"
11
+ "strings"
12
+ )
13
+
14
+ func openaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*OpenAIErrorWithStatusCode, string) {
15
+ responseText := ""
16
+ scanner := bufio.NewScanner(resp.Body)
17
+ scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
18
+ if atEOF && len(data) == 0 {
19
+ return 0, nil, nil
20
+ }
21
+ if i := strings.Index(string(data), "\n"); i >= 0 {
22
+ return i + 1, data[0:i], nil
23
+ }
24
+ if atEOF {
25
+ return len(data), data, nil
26
+ }
27
+ return 0, nil, nil
28
+ })
29
+ dataChan := make(chan string)
30
+ stopChan := make(chan bool)
31
+ go func() {
32
+ for scanner.Scan() {
33
+ data := scanner.Text()
34
+ if len(data) < 6 { // ignore blank line or wrong format
35
+ continue
36
+ }
37
+ if data[:6] != "data: " && data[:6] != "[DONE]" {
38
+ continue
39
+ }
40
+ dataChan <- data
41
+ data = data[6:]
42
+ if !strings.HasPrefix(data, "[DONE]") {
43
+ switch relayMode {
44
+ case RelayModeChatCompletions:
45
+ var streamResponse ChatCompletionsStreamResponse
46
+ err := json.Unmarshal([]byte(data), &streamResponse)
47
+ if err != nil {
48
+ common.SysError("error unmarshalling stream response: " + err.Error())
49
+ continue // just ignore the error
50
+ }
51
+ for _, choice := range streamResponse.Choices {
52
+ responseText += choice.Delta.Content
53
+ }
54
+ case RelayModeCompletions:
55
+ var streamResponse CompletionsStreamResponse
56
+ err := json.Unmarshal([]byte(data), &streamResponse)
57
+ if err != nil {
58
+ common.SysError("error unmarshalling stream response: " + err.Error())
59
+ continue
60
+ }
61
+ for _, choice := range streamResponse.Choices {
62
+ responseText += choice.Text
63
+ }
64
+ }
65
+ }
66
+ }
67
+ stopChan <- true
68
+ }()
69
+ setEventStreamHeaders(c)
70
+ c.Stream(func(w io.Writer) bool {
71
+ select {
72
+ case data := <-dataChan:
73
+ if strings.HasPrefix(data, "data: [DONE]") {
74
+ data = data[:12]
75
+ }
76
+ // some implementations may add \r at the end of data
77
+ data = strings.TrimSuffix(data, "\r")
78
+ c.Render(-1, common.CustomEvent{Data: data})
79
+ return true
80
+ case <-stopChan:
81
+ return false
82
+ }
83
+ })
84
+ err := resp.Body.Close()
85
+ if err != nil {
86
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), ""
87
+ }
88
+ return nil, responseText
89
+ }
90
+
91
+ func openaiHandler(c *gin.Context, resp *http.Response, consumeQuota bool, promptTokens int, model string) (*OpenAIErrorWithStatusCode, *Usage) {
92
+ var textResponse TextResponse
93
+ if consumeQuota {
94
+ responseBody, err := io.ReadAll(resp.Body)
95
+ if err != nil {
96
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
97
+ }
98
+ err = resp.Body.Close()
99
+ if err != nil {
100
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
101
+ }
102
+ err = json.Unmarshal(responseBody, &textResponse)
103
+ if err != nil {
104
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
105
+ }
106
+ if textResponse.Error.Type != "" {
107
+ return &OpenAIErrorWithStatusCode{
108
+ OpenAIError: textResponse.Error,
109
+ StatusCode: resp.StatusCode,
110
+ }, nil
111
+ }
112
+ // Reset response body
113
+ resp.Body = io.NopCloser(bytes.NewBuffer(responseBody))
114
+ }
115
+ // We shouldn't set the header before we parse the response body, because the parse part may fail.
116
+ // And then we will have to send an error response, but in this case, the header has already been set.
117
+ // So the httpClient will be confused by the response.
118
+ // For example, Postman will report error, and we cannot check the response at all.
119
+ for k, v := range resp.Header {
120
+ c.Writer.Header().Set(k, v[0])
121
+ }
122
+ c.Writer.WriteHeader(resp.StatusCode)
123
+ _, err := io.Copy(c.Writer, resp.Body)
124
+ if err != nil {
125
+ return errorWrapper(err, "copy_response_body_failed", http.StatusInternalServerError), nil
126
+ }
127
+ err = resp.Body.Close()
128
+ if err != nil {
129
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
130
+ }
131
+
132
+ if textResponse.Usage.TotalTokens == 0 {
133
+ completionTokens := 0
134
+ for _, choice := range textResponse.Choices {
135
+ completionTokens += countTokenText(choice.Message.Content, model)
136
+ }
137
+ textResponse.Usage = Usage{
138
+ PromptTokens: promptTokens,
139
+ CompletionTokens: completionTokens,
140
+ TotalTokens: promptTokens + completionTokens,
141
+ }
142
+ }
143
+ return nil, &textResponse.Usage
144
+ }
controller/relay-palm.go ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "github.com/gin-gonic/gin"
7
+ "io"
8
+ "net/http"
9
+ "one-api/common"
10
+ )
11
+
12
+ // https://developers.generativeai.google/api/rest/generativelanguage/models/generateMessage#request-body
13
+ // https://developers.generativeai.google/api/rest/generativelanguage/models/generateMessage#response-body
14
+
15
+ type PaLMChatMessage struct {
16
+ Author string `json:"author"`
17
+ Content string `json:"content"`
18
+ }
19
+
20
+ type PaLMFilter struct {
21
+ Reason string `json:"reason"`
22
+ Message string `json:"message"`
23
+ }
24
+
25
+ type PaLMPrompt struct {
26
+ Messages []PaLMChatMessage `json:"messages"`
27
+ }
28
+
29
+ type PaLMChatRequest struct {
30
+ Prompt PaLMPrompt `json:"prompt"`
31
+ Temperature float64 `json:"temperature,omitempty"`
32
+ CandidateCount int `json:"candidateCount,omitempty"`
33
+ TopP float64 `json:"topP,omitempty"`
34
+ TopK int `json:"topK,omitempty"`
35
+ }
36
+
37
+ type PaLMError struct {
38
+ Code int `json:"code"`
39
+ Message string `json:"message"`
40
+ Status string `json:"status"`
41
+ }
42
+
43
+ type PaLMChatResponse struct {
44
+ Candidates []PaLMChatMessage `json:"candidates"`
45
+ Messages []Message `json:"messages"`
46
+ Filters []PaLMFilter `json:"filters"`
47
+ Error PaLMError `json:"error"`
48
+ }
49
+
50
+ func requestOpenAI2PaLM(textRequest GeneralOpenAIRequest) *PaLMChatRequest {
51
+ palmRequest := PaLMChatRequest{
52
+ Prompt: PaLMPrompt{
53
+ Messages: make([]PaLMChatMessage, 0, len(textRequest.Messages)),
54
+ },
55
+ Temperature: textRequest.Temperature,
56
+ CandidateCount: textRequest.N,
57
+ TopP: textRequest.TopP,
58
+ TopK: textRequest.MaxTokens,
59
+ }
60
+ for _, message := range textRequest.Messages {
61
+ palmMessage := PaLMChatMessage{
62
+ Content: message.Content,
63
+ }
64
+ if message.Role == "user" {
65
+ palmMessage.Author = "0"
66
+ } else {
67
+ palmMessage.Author = "1"
68
+ }
69
+ palmRequest.Prompt.Messages = append(palmRequest.Prompt.Messages, palmMessage)
70
+ }
71
+ return &palmRequest
72
+ }
73
+
74
+ func responsePaLM2OpenAI(response *PaLMChatResponse) *OpenAITextResponse {
75
+ fullTextResponse := OpenAITextResponse{
76
+ Choices: make([]OpenAITextResponseChoice, 0, len(response.Candidates)),
77
+ }
78
+ for i, candidate := range response.Candidates {
79
+ choice := OpenAITextResponseChoice{
80
+ Index: i,
81
+ Message: Message{
82
+ Role: "assistant",
83
+ Content: candidate.Content,
84
+ },
85
+ FinishReason: "stop",
86
+ }
87
+ fullTextResponse.Choices = append(fullTextResponse.Choices, choice)
88
+ }
89
+ return &fullTextResponse
90
+ }
91
+
92
+ func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *ChatCompletionsStreamResponse {
93
+ var choice ChatCompletionsStreamResponseChoice
94
+ if len(palmResponse.Candidates) > 0 {
95
+ choice.Delta.Content = palmResponse.Candidates[0].Content
96
+ }
97
+ choice.FinishReason = &stopFinishReason
98
+ var response ChatCompletionsStreamResponse
99
+ response.Object = "chat.completion.chunk"
100
+ response.Model = "palm2"
101
+ response.Choices = []ChatCompletionsStreamResponseChoice{choice}
102
+ return &response
103
+ }
104
+
105
+ func palmStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, string) {
106
+ responseText := ""
107
+ responseId := fmt.Sprintf("chatcmpl-%s", common.GetUUID())
108
+ createdTime := common.GetTimestamp()
109
+ dataChan := make(chan string)
110
+ stopChan := make(chan bool)
111
+ go func() {
112
+ responseBody, err := io.ReadAll(resp.Body)
113
+ if err != nil {
114
+ common.SysError("error reading stream response: " + err.Error())
115
+ stopChan <- true
116
+ return
117
+ }
118
+ err = resp.Body.Close()
119
+ if err != nil {
120
+ common.SysError("error closing stream response: " + err.Error())
121
+ stopChan <- true
122
+ return
123
+ }
124
+ var palmResponse PaLMChatResponse
125
+ err = json.Unmarshal(responseBody, &palmResponse)
126
+ if err != nil {
127
+ common.SysError("error unmarshalling stream response: " + err.Error())
128
+ stopChan <- true
129
+ return
130
+ }
131
+ fullTextResponse := streamResponsePaLM2OpenAI(&palmResponse)
132
+ fullTextResponse.Id = responseId
133
+ fullTextResponse.Created = createdTime
134
+ if len(palmResponse.Candidates) > 0 {
135
+ responseText = palmResponse.Candidates[0].Content
136
+ }
137
+ jsonResponse, err := json.Marshal(fullTextResponse)
138
+ if err != nil {
139
+ common.SysError("error marshalling stream response: " + err.Error())
140
+ stopChan <- true
141
+ return
142
+ }
143
+ dataChan <- string(jsonResponse)
144
+ stopChan <- true
145
+ }()
146
+ setEventStreamHeaders(c)
147
+ c.Stream(func(w io.Writer) bool {
148
+ select {
149
+ case data := <-dataChan:
150
+ c.Render(-1, common.CustomEvent{Data: "data: " + data})
151
+ return true
152
+ case <-stopChan:
153
+ c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
154
+ return false
155
+ }
156
+ })
157
+ err := resp.Body.Close()
158
+ if err != nil {
159
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), ""
160
+ }
161
+ return nil, responseText
162
+ }
163
+
164
+ func palmHandler(c *gin.Context, resp *http.Response, promptTokens int, model string) (*OpenAIErrorWithStatusCode, *Usage) {
165
+ responseBody, err := io.ReadAll(resp.Body)
166
+ if err != nil {
167
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
168
+ }
169
+ err = resp.Body.Close()
170
+ if err != nil {
171
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
172
+ }
173
+ var palmResponse PaLMChatResponse
174
+ err = json.Unmarshal(responseBody, &palmResponse)
175
+ if err != nil {
176
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
177
+ }
178
+ if palmResponse.Error.Code != 0 || len(palmResponse.Candidates) == 0 {
179
+ return &OpenAIErrorWithStatusCode{
180
+ OpenAIError: OpenAIError{
181
+ Message: palmResponse.Error.Message,
182
+ Type: palmResponse.Error.Status,
183
+ Param: "",
184
+ Code: palmResponse.Error.Code,
185
+ },
186
+ StatusCode: resp.StatusCode,
187
+ }, nil
188
+ }
189
+ fullTextResponse := responsePaLM2OpenAI(&palmResponse)
190
+ completionTokens := countTokenText(palmResponse.Candidates[0].Content, model)
191
+ usage := Usage{
192
+ PromptTokens: promptTokens,
193
+ CompletionTokens: completionTokens,
194
+ TotalTokens: promptTokens + completionTokens,
195
+ }
196
+ fullTextResponse.Usage = usage
197
+ jsonResponse, err := json.Marshal(fullTextResponse)
198
+ if err != nil {
199
+ return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
200
+ }
201
+ c.Writer.Header().Set("Content-Type", "application/json")
202
+ c.Writer.WriteHeader(resp.StatusCode)
203
+ _, err = c.Writer.Write(jsonResponse)
204
+ return nil, &usage
205
+ }
controller/relay-text.go ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "github.com/gin-gonic/gin"
9
+ "io"
10
+ "net/http"
11
+ "one-api/common"
12
+ "one-api/model"
13
+ "strings"
14
+ "time"
15
+ )
16
+
17
+ const (
18
+ APITypeOpenAI = iota
19
+ APITypeClaude
20
+ APITypePaLM
21
+ APITypeBaidu
22
+ APITypeZhipu
23
+ APITypeAli
24
+ APITypeXunfei
25
+ )
26
+
27
+ var httpClient *http.Client
28
+ var impatientHTTPClient *http.Client
29
+
30
+ func init() {
31
+ httpClient = &http.Client{}
32
+ impatientHTTPClient = &http.Client{
33
+ Timeout: 5 * time.Second,
34
+ }
35
+ }
36
+
37
+ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
38
+ channelType := c.GetInt("channel")
39
+ tokenId := c.GetInt("token_id")
40
+ userId := c.GetInt("id")
41
+ consumeQuota := c.GetBool("consume_quota")
42
+ group := c.GetString("group")
43
+ var textRequest GeneralOpenAIRequest
44
+ if consumeQuota || channelType == common.ChannelTypeAzure || channelType == common.ChannelTypePaLM {
45
+ err := common.UnmarshalBodyReusable(c, &textRequest)
46
+ if err != nil {
47
+ return errorWrapper(err, "bind_request_body_failed", http.StatusBadRequest)
48
+ }
49
+ }
50
+ if relayMode == RelayModeModerations && textRequest.Model == "" {
51
+ textRequest.Model = "text-moderation-latest"
52
+ }
53
+ if relayMode == RelayModeEmbeddings && textRequest.Model == "" {
54
+ textRequest.Model = c.Param("model")
55
+ }
56
+ // request validation
57
+ if textRequest.Model == "" {
58
+ return errorWrapper(errors.New("model is required"), "required_field_missing", http.StatusBadRequest)
59
+ }
60
+ switch relayMode {
61
+ case RelayModeCompletions:
62
+ if textRequest.Prompt == "" {
63
+ return errorWrapper(errors.New("field prompt is required"), "required_field_missing", http.StatusBadRequest)
64
+ }
65
+ case RelayModeChatCompletions:
66
+ if textRequest.Messages == nil || len(textRequest.Messages) == 0 {
67
+ return errorWrapper(errors.New("field messages is required"), "required_field_missing", http.StatusBadRequest)
68
+ }
69
+ case RelayModeEmbeddings:
70
+ case RelayModeModerations:
71
+ if textRequest.Input == "" {
72
+ return errorWrapper(errors.New("field input is required"), "required_field_missing", http.StatusBadRequest)
73
+ }
74
+ case RelayModeEdits:
75
+ if textRequest.Instruction == "" {
76
+ return errorWrapper(errors.New("field instruction is required"), "required_field_missing", http.StatusBadRequest)
77
+ }
78
+ }
79
+ // map model name
80
+ modelMapping := c.GetString("model_mapping")
81
+ isModelMapped := false
82
+ if modelMapping != "" && modelMapping != "{}" {
83
+ modelMap := make(map[string]string)
84
+ err := json.Unmarshal([]byte(modelMapping), &modelMap)
85
+ if err != nil {
86
+ return errorWrapper(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError)
87
+ }
88
+ if modelMap[textRequest.Model] != "" {
89
+ textRequest.Model = modelMap[textRequest.Model]
90
+ isModelMapped = true
91
+ }
92
+ }
93
+ apiType := APITypeOpenAI
94
+ switch channelType {
95
+ case common.ChannelTypeAnthropic:
96
+ apiType = APITypeClaude
97
+ case common.ChannelTypeBaidu:
98
+ apiType = APITypeBaidu
99
+ case common.ChannelTypePaLM:
100
+ apiType = APITypePaLM
101
+ case common.ChannelTypeZhipu:
102
+ apiType = APITypeZhipu
103
+ case common.ChannelTypeAli:
104
+ apiType = APITypeAli
105
+ case common.ChannelTypeXunfei:
106
+ apiType = APITypeXunfei
107
+ }
108
+ baseURL := common.ChannelBaseURLs[channelType]
109
+ requestURL := c.Request.URL.String()
110
+ if c.GetString("base_url") != "" {
111
+ baseURL = c.GetString("base_url")
112
+ }
113
+ fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL)
114
+ switch apiType {
115
+ case APITypeOpenAI:
116
+ if channelType == common.ChannelTypeAzure {
117
+ // https://learn.microsoft.com/en-us/azure/cognitive-services/openai/chatgpt-quickstart?pivots=rest-api&tabs=command-line#rest-api
118
+ query := c.Request.URL.Query()
119
+ apiVersion := query.Get("api-version")
120
+ if apiVersion == "" {
121
+ apiVersion = c.GetString("api_version")
122
+ }
123
+ requestURL := strings.Split(requestURL, "?")[0]
124
+ requestURL = fmt.Sprintf("%s?api-version=%s", requestURL, apiVersion)
125
+ baseURL = c.GetString("base_url")
126
+ task := strings.TrimPrefix(requestURL, "/v1/")
127
+ model_ := textRequest.Model
128
+ model_ = strings.Replace(model_, ".", "", -1)
129
+ // https://github.com/songquanpeng/one-api/issues/67
130
+ model_ = strings.TrimSuffix(model_, "-0301")
131
+ model_ = strings.TrimSuffix(model_, "-0314")
132
+ model_ = strings.TrimSuffix(model_, "-0613")
133
+ fullRequestURL = fmt.Sprintf("%s/openai/deployments/%s/%s", baseURL, model_, task)
134
+ }
135
+ case APITypeClaude:
136
+ fullRequestURL = "https://api.anthropic.com/v1/complete"
137
+ if baseURL != "" {
138
+ fullRequestURL = fmt.Sprintf("%s/v1/complete", baseURL)
139
+ }
140
+ case APITypeBaidu:
141
+ switch textRequest.Model {
142
+ case "ERNIE-Bot":
143
+ fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"
144
+ case "ERNIE-Bot-turbo":
145
+ fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant"
146
+ case "BLOOMZ-7B":
147
+ fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/bloomz_7b1"
148
+ case "Embedding-V1":
149
+ fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/embedding-v1"
150
+ }
151
+ apiKey := c.Request.Header.Get("Authorization")
152
+ apiKey = strings.TrimPrefix(apiKey, "Bearer ")
153
+ var err error
154
+ if apiKey, err = getBaiduAccessToken(apiKey); err != nil {
155
+ return errorWrapper(err, "invalid_baidu_config", http.StatusInternalServerError)
156
+ }
157
+ fullRequestURL += "?access_token=" + apiKey
158
+ case APITypePaLM:
159
+ fullRequestURL = "https://generativelanguage.googleapis.com/v1beta2/models/chat-bison-001:generateMessage"
160
+ if baseURL != "" {
161
+ fullRequestURL = fmt.Sprintf("%s/v1beta2/models/chat-bison-001:generateMessage", baseURL)
162
+ }
163
+ apiKey := c.Request.Header.Get("Authorization")
164
+ apiKey = strings.TrimPrefix(apiKey, "Bearer ")
165
+ fullRequestURL += "?key=" + apiKey
166
+ case APITypeZhipu:
167
+ method := "invoke"
168
+ if textRequest.Stream {
169
+ method = "sse-invoke"
170
+ }
171
+ fullRequestURL = fmt.Sprintf("https://open.bigmodel.cn/api/paas/v3/model-api/%s/%s", textRequest.Model, method)
172
+ case APITypeAli:
173
+ fullRequestURL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
174
+ }
175
+ var promptTokens int
176
+ var completionTokens int
177
+ switch relayMode {
178
+ case RelayModeChatCompletions:
179
+ promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model)
180
+ case RelayModeCompletions:
181
+ promptTokens = countTokenInput(textRequest.Prompt, textRequest.Model)
182
+ case RelayModeModerations:
183
+ promptTokens = countTokenInput(textRequest.Input, textRequest.Model)
184
+ }
185
+ preConsumedTokens := common.PreConsumedQuota
186
+ if textRequest.MaxTokens != 0 {
187
+ preConsumedTokens = promptTokens + textRequest.MaxTokens
188
+ }
189
+ modelRatio := common.GetModelRatio(textRequest.Model)
190
+ groupRatio := common.GetGroupRatio(group)
191
+ ratio := modelRatio * groupRatio
192
+ preConsumedQuota := int(float64(preConsumedTokens) * ratio)
193
+ userQuota, err := model.CacheGetUserQuota(userId)
194
+ if err != nil {
195
+ return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError)
196
+ }
197
+ err = model.CacheDecreaseUserQuota(userId, preConsumedQuota)
198
+ if err != nil {
199
+ return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError)
200
+ }
201
+ if userQuota > 100*preConsumedQuota {
202
+ // in this case, we do not pre-consume quota
203
+ // because the user has enough quota
204
+ preConsumedQuota = 0
205
+ }
206
+ if consumeQuota && preConsumedQuota > 0 {
207
+ err := model.PreConsumeTokenQuota(tokenId, preConsumedQuota)
208
+ if err != nil {
209
+ return errorWrapper(err, "pre_consume_token_quota_failed", http.StatusForbidden)
210
+ }
211
+ }
212
+ var requestBody io.Reader
213
+ if isModelMapped {
214
+ jsonStr, err := json.Marshal(textRequest)
215
+ if err != nil {
216
+ return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError)
217
+ }
218
+ requestBody = bytes.NewBuffer(jsonStr)
219
+ } else {
220
+ requestBody = c.Request.Body
221
+ }
222
+ switch apiType {
223
+ case APITypeClaude:
224
+ claudeRequest := requestOpenAI2Claude(textRequest)
225
+ jsonStr, err := json.Marshal(claudeRequest)
226
+ if err != nil {
227
+ return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError)
228
+ }
229
+ requestBody = bytes.NewBuffer(jsonStr)
230
+ case APITypeBaidu:
231
+ var jsonData []byte
232
+ var err error
233
+ switch relayMode {
234
+ case RelayModeEmbeddings:
235
+ baiduEmbeddingRequest := embeddingRequestOpenAI2Baidu(textRequest)
236
+ jsonData, err = json.Marshal(baiduEmbeddingRequest)
237
+ default:
238
+ baiduRequest := requestOpenAI2Baidu(textRequest)
239
+ jsonData, err = json.Marshal(baiduRequest)
240
+ }
241
+ if err != nil {
242
+ return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError)
243
+ }
244
+ requestBody = bytes.NewBuffer(jsonData)
245
+ case APITypePaLM:
246
+ palmRequest := requestOpenAI2PaLM(textRequest)
247
+ jsonStr, err := json.Marshal(palmRequest)
248
+ if err != nil {
249
+ return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError)
250
+ }
251
+ requestBody = bytes.NewBuffer(jsonStr)
252
+ case APITypeZhipu:
253
+ zhipuRequest := requestOpenAI2Zhipu(textRequest)
254
+ jsonStr, err := json.Marshal(zhipuRequest)
255
+ if err != nil {
256
+ return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError)
257
+ }
258
+ requestBody = bytes.NewBuffer(jsonStr)
259
+ case APITypeAli:
260
+ aliRequest := requestOpenAI2Ali(textRequest)
261
+ jsonStr, err := json.Marshal(aliRequest)
262
+ if err != nil {
263
+ return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError)
264
+ }
265
+ requestBody = bytes.NewBuffer(jsonStr)
266
+ }
267
+
268
+ var req *http.Request
269
+ var resp *http.Response
270
+ isStream := textRequest.Stream
271
+
272
+ if apiType != APITypeXunfei { // cause xunfei use websocket
273
+ req, err = http.NewRequest(c.Request.Method, fullRequestURL, requestBody)
274
+ if err != nil {
275
+ return errorWrapper(err, "new_request_failed", http.StatusInternalServerError)
276
+ }
277
+ apiKey := c.Request.Header.Get("Authorization")
278
+ apiKey = strings.TrimPrefix(apiKey, "Bearer ")
279
+ switch apiType {
280
+ case APITypeOpenAI:
281
+ if channelType == common.ChannelTypeAzure {
282
+ req.Header.Set("api-key", apiKey)
283
+ } else {
284
+ req.Header.Set("Authorization", c.Request.Header.Get("Authorization"))
285
+ if channelType == common.ChannelTypeOpenRouter {
286
+ req.Header.Set("HTTP-Referer", "https://github.com/songquanpeng/one-api")
287
+ req.Header.Set("X-Title", "One API")
288
+ }
289
+ }
290
+ case APITypeClaude:
291
+ req.Header.Set("x-api-key", apiKey)
292
+ anthropicVersion := c.Request.Header.Get("anthropic-version")
293
+ if anthropicVersion == "" {
294
+ anthropicVersion = "2023-06-01"
295
+ }
296
+ req.Header.Set("anthropic-version", anthropicVersion)
297
+ case APITypeZhipu:
298
+ token := getZhipuToken(apiKey)
299
+ req.Header.Set("Authorization", token)
300
+ case APITypeAli:
301
+ req.Header.Set("Authorization", "Bearer "+apiKey)
302
+ if textRequest.Stream {
303
+ req.Header.Set("X-DashScope-SSE", "enable")
304
+ }
305
+ }
306
+ req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
307
+ req.Header.Set("Accept", c.Request.Header.Get("Accept"))
308
+ //req.Header.Set("Connection", c.Request.Header.Get("Connection"))
309
+ resp, err = httpClient.Do(req)
310
+ if err != nil {
311
+ return errorWrapper(err, "do_request_failed", http.StatusInternalServerError)
312
+ }
313
+ err = req.Body.Close()
314
+ if err != nil {
315
+ return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError)
316
+ }
317
+ err = c.Request.Body.Close()
318
+ if err != nil {
319
+ return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError)
320
+ }
321
+ isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream")
322
+
323
+ if resp.StatusCode != http.StatusOK {
324
+ return relayErrorHandler(resp)
325
+ }
326
+ }
327
+
328
+ var textResponse TextResponse
329
+ tokenName := c.GetString("token_name")
330
+ channelId := c.GetInt("channel_id")
331
+
332
+ defer func() {
333
+ // c.Writer.Flush()
334
+ go func() {
335
+ if consumeQuota {
336
+ quota := 0
337
+ completionRatio := common.GetCompletionRatio(textRequest.Model)
338
+ promptTokens = textResponse.Usage.PromptTokens
339
+ completionTokens = textResponse.Usage.CompletionTokens
340
+
341
+ quota = promptTokens + int(float64(completionTokens)*completionRatio)
342
+ quota = int(float64(quota) * ratio)
343
+ if ratio != 0 && quota <= 0 {
344
+ quota = 1
345
+ }
346
+ totalTokens := promptTokens + completionTokens
347
+ if totalTokens == 0 {
348
+ // in this case, must be some error happened
349
+ // we cannot just return, because we may have to return the pre-consumed quota
350
+ quota = 0
351
+ }
352
+ quotaDelta := quota - preConsumedQuota
353
+ err := model.PostConsumeTokenQuota(tokenId, quotaDelta)
354
+ if err != nil {
355
+ common.SysError("error consuming token remain quota: " + err.Error())
356
+ }
357
+ err = model.CacheUpdateUserQuota(userId)
358
+ if err != nil {
359
+ common.SysError("error update user quota cache: " + err.Error())
360
+ }
361
+ if quota != 0 {
362
+ logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
363
+ model.RecordConsumeLog(userId, promptTokens, completionTokens, textRequest.Model, tokenName, quota, logContent)
364
+ model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
365
+
366
+ model.UpdateChannelUsedQuota(channelId, quota)
367
+ }
368
+ }
369
+ }()
370
+ }()
371
+ switch apiType {
372
+ case APITypeOpenAI:
373
+ if isStream {
374
+ err, responseText := openaiStreamHandler(c, resp, relayMode)
375
+ if err != nil {
376
+ return err
377
+ }
378
+ textResponse.Usage.PromptTokens = promptTokens
379
+ textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model)
380
+ return nil
381
+ } else {
382
+ err, usage := openaiHandler(c, resp, consumeQuota, promptTokens, textRequest.Model)
383
+ if err != nil {
384
+ return err
385
+ }
386
+ if usage != nil {
387
+ textResponse.Usage = *usage
388
+ }
389
+ return nil
390
+ }
391
+ case APITypeClaude:
392
+ if isStream {
393
+ err, responseText := claudeStreamHandler(c, resp)
394
+ if err != nil {
395
+ return err
396
+ }
397
+ textResponse.Usage.PromptTokens = promptTokens
398
+ textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model)
399
+ return nil
400
+ } else {
401
+ err, usage := claudeHandler(c, resp, promptTokens, textRequest.Model)
402
+ if err != nil {
403
+ return err
404
+ }
405
+ if usage != nil {
406
+ textResponse.Usage = *usage
407
+ }
408
+ return nil
409
+ }
410
+ case APITypeBaidu:
411
+ if isStream {
412
+ err, usage := baiduStreamHandler(c, resp)
413
+ if err != nil {
414
+ return err
415
+ }
416
+ if usage != nil {
417
+ textResponse.Usage = *usage
418
+ }
419
+ return nil
420
+ } else {
421
+ var err *OpenAIErrorWithStatusCode
422
+ var usage *Usage
423
+ switch relayMode {
424
+ case RelayModeEmbeddings:
425
+ err, usage = baiduEmbeddingHandler(c, resp)
426
+ default:
427
+ err, usage = baiduHandler(c, resp)
428
+ }
429
+ if err != nil {
430
+ return err
431
+ }
432
+ if usage != nil {
433
+ textResponse.Usage = *usage
434
+ }
435
+ return nil
436
+ }
437
+ case APITypePaLM:
438
+ if textRequest.Stream { // PaLM2 API does not support stream
439
+ err, responseText := palmStreamHandler(c, resp)
440
+ if err != nil {
441
+ return err
442
+ }
443
+ textResponse.Usage.PromptTokens = promptTokens
444
+ textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model)
445
+ return nil
446
+ } else {
447
+ err, usage := palmHandler(c, resp, promptTokens, textRequest.Model)
448
+ if err != nil {
449
+ return err
450
+ }
451
+ if usage != nil {
452
+ textResponse.Usage = *usage
453
+ }
454
+ return nil
455
+ }
456
+ case APITypeZhipu:
457
+ if isStream {
458
+ err, usage := zhipuStreamHandler(c, resp)
459
+ if err != nil {
460
+ return err
461
+ }
462
+ if usage != nil {
463
+ textResponse.Usage = *usage
464
+ }
465
+ // zhipu's API does not return prompt tokens & completion tokens
466
+ textResponse.Usage.PromptTokens = textResponse.Usage.TotalTokens
467
+ return nil
468
+ } else {
469
+ err, usage := zhipuHandler(c, resp)
470
+ if err != nil {
471
+ return err
472
+ }
473
+ if usage != nil {
474
+ textResponse.Usage = *usage
475
+ }
476
+ // zhipu's API does not return prompt tokens & completion tokens
477
+ textResponse.Usage.PromptTokens = textResponse.Usage.TotalTokens
478
+ return nil
479
+ }
480
+ case APITypeAli:
481
+ if isStream {
482
+ err, usage := aliStreamHandler(c, resp)
483
+ if err != nil {
484
+ return err
485
+ }
486
+ if usage != nil {
487
+ textResponse.Usage = *usage
488
+ }
489
+ return nil
490
+ } else {
491
+ err, usage := aliHandler(c, resp)
492
+ if err != nil {
493
+ return err
494
+ }
495
+ if usage != nil {
496
+ textResponse.Usage = *usage
497
+ }
498
+ return nil
499
+ }
500
+ case APITypeXunfei:
501
+ if isStream {
502
+ auth := c.Request.Header.Get("Authorization")
503
+ auth = strings.TrimPrefix(auth, "Bearer ")
504
+ splits := strings.Split(auth, "|")
505
+ if len(splits) != 3 {
506
+ return errorWrapper(errors.New("invalid auth"), "invalid_auth", http.StatusBadRequest)
507
+ }
508
+ err, usage := xunfeiStreamHandler(c, textRequest, splits[0], splits[1], splits[2])
509
+ if err != nil {
510
+ return err
511
+ }
512
+ if usage != nil {
513
+ textResponse.Usage = *usage
514
+ }
515
+ return nil
516
+ } else {
517
+ return errorWrapper(errors.New("xunfei api does not support non-stream mode"), "invalid_api_type", http.StatusBadRequest)
518
+ }
519
+ default:
520
+ return errorWrapper(errors.New("unknown api type"), "unknown_api_type", http.StatusInternalServerError)
521
+ }
522
+ }
controller/relay-utils.go ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "github.com/gin-gonic/gin"
7
+ "github.com/pkoukk/tiktoken-go"
8
+ "io"
9
+ "net/http"
10
+ "one-api/common"
11
+ "strconv"
12
+ )
13
+
14
+ var stopFinishReason = "stop"
15
+
16
+ var tokenEncoderMap = map[string]*tiktoken.Tiktoken{}
17
+
18
+ func InitTokenEncoders() {
19
+ common.SysLog("initializing token encoders")
20
+ fallbackTokenEncoder, err := tiktoken.EncodingForModel("gpt-3.5-turbo")
21
+ if err != nil {
22
+ common.FatalLog(fmt.Sprintf("failed to get fallback token encoder: %s", err.Error()))
23
+ }
24
+ for model, _ := range common.ModelRatio {
25
+ tokenEncoder, err := tiktoken.EncodingForModel(model)
26
+ if err != nil {
27
+ common.SysError(fmt.Sprintf("using fallback encoder for model %s", model))
28
+ tokenEncoderMap[model] = fallbackTokenEncoder
29
+ continue
30
+ }
31
+ tokenEncoderMap[model] = tokenEncoder
32
+ }
33
+ common.SysLog("token encoders initialized")
34
+ }
35
+
36
+ func getTokenEncoder(model string) *tiktoken.Tiktoken {
37
+ if tokenEncoder, ok := tokenEncoderMap[model]; ok {
38
+ return tokenEncoder
39
+ }
40
+ tokenEncoder, err := tiktoken.EncodingForModel(model)
41
+ if err != nil {
42
+ common.SysError(fmt.Sprintf("failed to get token encoder for model %s: %s, using encoder for gpt-3.5-turbo", model, err.Error()))
43
+ tokenEncoder, err = tiktoken.EncodingForModel("gpt-3.5-turbo")
44
+ if err != nil {
45
+ common.FatalLog(fmt.Sprintf("failed to get token encoder for model gpt-3.5-turbo: %s", err.Error()))
46
+ }
47
+ }
48
+ tokenEncoderMap[model] = tokenEncoder
49
+ return tokenEncoder
50
+ }
51
+
52
+ func getTokenNum(tokenEncoder *tiktoken.Tiktoken, text string) int {
53
+ if common.ApproximateTokenEnabled {
54
+ return int(float64(len(text)) * 0.38)
55
+ }
56
+ return len(tokenEncoder.Encode(text, nil, nil))
57
+ }
58
+
59
+ func countTokenMessages(messages []Message, model string) int {
60
+ tokenEncoder := getTokenEncoder(model)
61
+ // Reference:
62
+ // https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
63
+ // https://github.com/pkoukk/tiktoken-go/issues/6
64
+ //
65
+ // Every message follows <|start|>{role/name}\n{content}<|end|>\n
66
+ var tokensPerMessage int
67
+ var tokensPerName int
68
+ if model == "gpt-3.5-turbo-0301" {
69
+ tokensPerMessage = 4
70
+ tokensPerName = -1 // If there's a name, the role is omitted
71
+ } else {
72
+ tokensPerMessage = 3
73
+ tokensPerName = 1
74
+ }
75
+ tokenNum := 0
76
+ for _, message := range messages {
77
+ tokenNum += tokensPerMessage
78
+ tokenNum += getTokenNum(tokenEncoder, message.Content)
79
+ tokenNum += getTokenNum(tokenEncoder, message.Role)
80
+ if message.Name != nil {
81
+ tokenNum += tokensPerName
82
+ tokenNum += getTokenNum(tokenEncoder, *message.Name)
83
+ }
84
+ }
85
+ tokenNum += 3 // Every reply is primed with <|start|>assistant<|message|>
86
+ return tokenNum
87
+ }
88
+
89
+ func countTokenInput(input any, model string) int {
90
+ switch input.(type) {
91
+ case string:
92
+ return countTokenText(input.(string), model)
93
+ case []string:
94
+ text := ""
95
+ for _, s := range input.([]string) {
96
+ text += s
97
+ }
98
+ return countTokenText(text, model)
99
+ }
100
+ return 0
101
+ }
102
+
103
+ func countTokenText(text string, model string) int {
104
+ tokenEncoder := getTokenEncoder(model)
105
+ return getTokenNum(tokenEncoder, text)
106
+ }
107
+
108
+ func errorWrapper(err error, code string, statusCode int) *OpenAIErrorWithStatusCode {
109
+ openAIError := OpenAIError{
110
+ Message: err.Error(),
111
+ Type: "one_api_error",
112
+ Code: code,
113
+ }
114
+ return &OpenAIErrorWithStatusCode{
115
+ OpenAIError: openAIError,
116
+ StatusCode: statusCode,
117
+ }
118
+ }
119
+
120
+ func shouldDisableChannel(err *OpenAIError, statusCode int) bool {
121
+ if !common.AutomaticDisableChannelEnabled {
122
+ return false
123
+ }
124
+ if err == nil {
125
+ return false
126
+ }
127
+ if statusCode == http.StatusUnauthorized {
128
+ return true
129
+ }
130
+ if err.Type == "insufficient_quota" || err.Code == "invalid_api_key" || err.Code == "account_deactivated" {
131
+ return true
132
+ }
133
+ return false
134
+ }
135
+
136
+ func setEventStreamHeaders(c *gin.Context) {
137
+ c.Writer.Header().Set("Content-Type", "text/event-stream")
138
+ c.Writer.Header().Set("Cache-Control", "no-cache")
139
+ c.Writer.Header().Set("Connection", "keep-alive")
140
+ c.Writer.Header().Set("Transfer-Encoding", "chunked")
141
+ c.Writer.Header().Set("X-Accel-Buffering", "no")
142
+ }
143
+
144
+ func relayErrorHandler(resp *http.Response) (openAIErrorWithStatusCode *OpenAIErrorWithStatusCode) {
145
+ openAIErrorWithStatusCode = &OpenAIErrorWithStatusCode{
146
+ StatusCode: resp.StatusCode,
147
+ OpenAIError: OpenAIError{
148
+ Message: fmt.Sprintf("bad response status code %d", resp.StatusCode),
149
+ Type: "one_api_error",
150
+ Code: "bad_response_status_code",
151
+ Param: strconv.Itoa(resp.StatusCode),
152
+ },
153
+ }
154
+ responseBody, err := io.ReadAll(resp.Body)
155
+ if err != nil {
156
+ return
157
+ }
158
+ err = resp.Body.Close()
159
+ if err != nil {
160
+ return
161
+ }
162
+ var textResponse TextResponse
163
+ err = json.Unmarshal(responseBody, &textResponse)
164
+ if err != nil {
165
+ return
166
+ }
167
+ openAIErrorWithStatusCode.OpenAIError = textResponse.Error
168
+ return
169
+ }
controller/relay-xunfei.go ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "crypto/hmac"
5
+ "crypto/sha256"
6
+ "encoding/base64"
7
+ "encoding/json"
8
+ "fmt"
9
+ "github.com/gin-gonic/gin"
10
+ "github.com/gorilla/websocket"
11
+ "io"
12
+ "net/http"
13
+ "net/url"
14
+ "one-api/common"
15
+ "strings"
16
+ "time"
17
+ )
18
+
19
+ // https://console.xfyun.cn/services/cbm
20
+ // https://www.xfyun.cn/doc/spark/Web.html
21
+
22
+ type XunfeiMessage struct {
23
+ Role string `json:"role"`
24
+ Content string `json:"content"`
25
+ }
26
+
27
+ type XunfeiChatRequest struct {
28
+ Header struct {
29
+ AppId string `json:"app_id"`
30
+ } `json:"header"`
31
+ Parameter struct {
32
+ Chat struct {
33
+ Domain string `json:"domain,omitempty"`
34
+ Temperature float64 `json:"temperature,omitempty"`
35
+ TopK int `json:"top_k,omitempty"`
36
+ MaxTokens int `json:"max_tokens,omitempty"`
37
+ Auditing bool `json:"auditing,omitempty"`
38
+ } `json:"chat"`
39
+ } `json:"parameter"`
40
+ Payload struct {
41
+ Message struct {
42
+ Text []XunfeiMessage `json:"text"`
43
+ } `json:"message"`
44
+ } `json:"payload"`
45
+ }
46
+
47
+ type XunfeiChatResponseTextItem struct {
48
+ Content string `json:"content"`
49
+ Role string `json:"role"`
50
+ Index int `json:"index"`
51
+ }
52
+
53
+ type XunfeiChatResponse struct {
54
+ Header struct {
55
+ Code int `json:"code"`
56
+ Message string `json:"message"`
57
+ Sid string `json:"sid"`
58
+ Status int `json:"status"`
59
+ } `json:"header"`
60
+ Payload struct {
61
+ Choices struct {
62
+ Status int `json:"status"`
63
+ Seq int `json:"seq"`
64
+ Text []XunfeiChatResponseTextItem `json:"text"`
65
+ } `json:"choices"`
66
+ Usage struct {
67
+ //Text struct {
68
+ // QuestionTokens string `json:"question_tokens"`
69
+ // PromptTokens string `json:"prompt_tokens"`
70
+ // CompletionTokens string `json:"completion_tokens"`
71
+ // TotalTokens string `json:"total_tokens"`
72
+ //} `json:"text"`
73
+ Text Usage `json:"text"`
74
+ } `json:"usage"`
75
+ } `json:"payload"`
76
+ }
77
+
78
+ func requestOpenAI2Xunfei(request GeneralOpenAIRequest, xunfeiAppId string, domain string) *XunfeiChatRequest {
79
+ messages := make([]XunfeiMessage, 0, len(request.Messages))
80
+ for _, message := range request.Messages {
81
+ if message.Role == "system" {
82
+ messages = append(messages, XunfeiMessage{
83
+ Role: "user",
84
+ Content: message.Content,
85
+ })
86
+ messages = append(messages, XunfeiMessage{
87
+ Role: "assistant",
88
+ Content: "Okay",
89
+ })
90
+ } else {
91
+ messages = append(messages, XunfeiMessage{
92
+ Role: message.Role,
93
+ Content: message.Content,
94
+ })
95
+ }
96
+ }
97
+ xunfeiRequest := XunfeiChatRequest{}
98
+ xunfeiRequest.Header.AppId = xunfeiAppId
99
+ xunfeiRequest.Parameter.Chat.Domain = domain
100
+ xunfeiRequest.Parameter.Chat.Temperature = request.Temperature
101
+ xunfeiRequest.Parameter.Chat.TopK = request.N
102
+ xunfeiRequest.Parameter.Chat.MaxTokens = request.MaxTokens
103
+ xunfeiRequest.Payload.Message.Text = messages
104
+ return &xunfeiRequest
105
+ }
106
+
107
+ func responseXunfei2OpenAI(response *XunfeiChatResponse) *OpenAITextResponse {
108
+ if len(response.Payload.Choices.Text) == 0 {
109
+ response.Payload.Choices.Text = []XunfeiChatResponseTextItem{
110
+ {
111
+ Content: "",
112
+ },
113
+ }
114
+ }
115
+ choice := OpenAITextResponseChoice{
116
+ Index: 0,
117
+ Message: Message{
118
+ Role: "assistant",
119
+ Content: response.Payload.Choices.Text[0].Content,
120
+ },
121
+ }
122
+ fullTextResponse := OpenAITextResponse{
123
+ Object: "chat.completion",
124
+ Created: common.GetTimestamp(),
125
+ Choices: []OpenAITextResponseChoice{choice},
126
+ Usage: response.Payload.Usage.Text,
127
+ }
128
+ return &fullTextResponse
129
+ }
130
+
131
+ func streamResponseXunfei2OpenAI(xunfeiResponse *XunfeiChatResponse) *ChatCompletionsStreamResponse {
132
+ if len(xunfeiResponse.Payload.Choices.Text) == 0 {
133
+ xunfeiResponse.Payload.Choices.Text = []XunfeiChatResponseTextItem{
134
+ {
135
+ Content: "",
136
+ },
137
+ }
138
+ }
139
+ var choice ChatCompletionsStreamResponseChoice
140
+ choice.Delta.Content = xunfeiResponse.Payload.Choices.Text[0].Content
141
+ if xunfeiResponse.Payload.Choices.Status == 2 {
142
+ choice.FinishReason = &stopFinishReason
143
+ }
144
+ response := ChatCompletionsStreamResponse{
145
+ Object: "chat.completion.chunk",
146
+ Created: common.GetTimestamp(),
147
+ Model: "SparkDesk",
148
+ Choices: []ChatCompletionsStreamResponseChoice{choice},
149
+ }
150
+ return &response
151
+ }
152
+
153
+ func buildXunfeiAuthUrl(hostUrl string, apiKey, apiSecret string) string {
154
+ HmacWithShaToBase64 := func(algorithm, data, key string) string {
155
+ mac := hmac.New(sha256.New, []byte(key))
156
+ mac.Write([]byte(data))
157
+ encodeData := mac.Sum(nil)
158
+ return base64.StdEncoding.EncodeToString(encodeData)
159
+ }
160
+ ul, err := url.Parse(hostUrl)
161
+ if err != nil {
162
+ fmt.Println(err)
163
+ }
164
+ date := time.Now().UTC().Format(time.RFC1123)
165
+ signString := []string{"host: " + ul.Host, "date: " + date, "GET " + ul.Path + " HTTP/1.1"}
166
+ sign := strings.Join(signString, "\n")
167
+ sha := HmacWithShaToBase64("hmac-sha256", sign, apiSecret)
168
+ authUrl := fmt.Sprintf("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey,
169
+ "hmac-sha256", "host date request-line", sha)
170
+ authorization := base64.StdEncoding.EncodeToString([]byte(authUrl))
171
+ v := url.Values{}
172
+ v.Add("host", ul.Host)
173
+ v.Add("date", date)
174
+ v.Add("authorization", authorization)
175
+ callUrl := hostUrl + "?" + v.Encode()
176
+ return callUrl
177
+ }
178
+
179
+ func xunfeiStreamHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*OpenAIErrorWithStatusCode, *Usage) {
180
+ var usage Usage
181
+ query := c.Request.URL.Query()
182
+ apiVersion := query.Get("api-version")
183
+ if apiVersion == "" {
184
+ apiVersion = c.GetString("api_version")
185
+ }
186
+ if apiVersion == "" {
187
+ apiVersion = "v1.1"
188
+ common.SysLog("api_version not found, use default: " + apiVersion)
189
+ }
190
+ domain := "general"
191
+ if apiVersion == "v2.1" {
192
+ domain = "generalv2"
193
+ }
194
+ hostUrl := fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion)
195
+ d := websocket.Dialer{
196
+ HandshakeTimeout: 5 * time.Second,
197
+ }
198
+ conn, resp, err := d.Dial(buildXunfeiAuthUrl(hostUrl, apiKey, apiSecret), nil)
199
+ if err != nil || resp.StatusCode != 101 {
200
+ return errorWrapper(err, "dial_failed", http.StatusInternalServerError), nil
201
+ }
202
+ data := requestOpenAI2Xunfei(textRequest, appId, domain)
203
+ err = conn.WriteJSON(data)
204
+ if err != nil {
205
+ return errorWrapper(err, "write_json_failed", http.StatusInternalServerError), nil
206
+ }
207
+ dataChan := make(chan XunfeiChatResponse)
208
+ stopChan := make(chan bool)
209
+ go func() {
210
+ for {
211
+ _, msg, err := conn.ReadMessage()
212
+ if err != nil {
213
+ common.SysError("error reading stream response: " + err.Error())
214
+ break
215
+ }
216
+ var response XunfeiChatResponse
217
+ err = json.Unmarshal(msg, &response)
218
+ if err != nil {
219
+ common.SysError("error unmarshalling stream response: " + err.Error())
220
+ break
221
+ }
222
+ dataChan <- response
223
+ if response.Payload.Choices.Status == 2 {
224
+ err := conn.Close()
225
+ if err != nil {
226
+ common.SysError("error closing websocket connection: " + err.Error())
227
+ }
228
+ break
229
+ }
230
+ }
231
+ stopChan <- true
232
+ }()
233
+ setEventStreamHeaders(c)
234
+ c.Stream(func(w io.Writer) bool {
235
+ select {
236
+ case xunfeiResponse := <-dataChan:
237
+ usage.PromptTokens += xunfeiResponse.Payload.Usage.Text.PromptTokens
238
+ usage.CompletionTokens += xunfeiResponse.Payload.Usage.Text.CompletionTokens
239
+ usage.TotalTokens += xunfeiResponse.Payload.Usage.Text.TotalTokens
240
+ response := streamResponseXunfei2OpenAI(&xunfeiResponse)
241
+ jsonResponse, err := json.Marshal(response)
242
+ if err != nil {
243
+ common.SysError("error marshalling stream response: " + err.Error())
244
+ return true
245
+ }
246
+ c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)})
247
+ return true
248
+ case <-stopChan:
249
+ c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
250
+ return false
251
+ }
252
+ })
253
+ return nil, &usage
254
+ }
255
+
256
+ func xunfeiHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
257
+ var xunfeiResponse XunfeiChatResponse
258
+ responseBody, err := io.ReadAll(resp.Body)
259
+ if err != nil {
260
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
261
+ }
262
+ err = resp.Body.Close()
263
+ if err != nil {
264
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
265
+ }
266
+ err = json.Unmarshal(responseBody, &xunfeiResponse)
267
+ if err != nil {
268
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
269
+ }
270
+ if xunfeiResponse.Header.Code != 0 {
271
+ return &OpenAIErrorWithStatusCode{
272
+ OpenAIError: OpenAIError{
273
+ Message: xunfeiResponse.Header.Message,
274
+ Type: "xunfei_error",
275
+ Param: "",
276
+ Code: xunfeiResponse.Header.Code,
277
+ },
278
+ StatusCode: resp.StatusCode,
279
+ }, nil
280
+ }
281
+ fullTextResponse := responseXunfei2OpenAI(&xunfeiResponse)
282
+ jsonResponse, err := json.Marshal(fullTextResponse)
283
+ if err != nil {
284
+ return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
285
+ }
286
+ c.Writer.Header().Set("Content-Type", "application/json")
287
+ c.Writer.WriteHeader(resp.StatusCode)
288
+ _, err = c.Writer.Write(jsonResponse)
289
+ return nil, &fullTextResponse.Usage
290
+ }
controller/relay-zhipu.go ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "bufio"
5
+ "encoding/json"
6
+ "github.com/gin-gonic/gin"
7
+ "github.com/golang-jwt/jwt"
8
+ "io"
9
+ "net/http"
10
+ "one-api/common"
11
+ "strings"
12
+ "sync"
13
+ "time"
14
+ )
15
+
16
+ // https://open.bigmodel.cn/doc/api#chatglm_std
17
+ // chatglm_std, chatglm_lite
18
+ // https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/invoke
19
+ // https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/sse-invoke
20
+
21
+ type ZhipuMessage struct {
22
+ Role string `json:"role"`
23
+ Content string `json:"content"`
24
+ }
25
+
26
+ type ZhipuRequest struct {
27
+ Prompt []ZhipuMessage `json:"prompt"`
28
+ Temperature float64 `json:"temperature,omitempty"`
29
+ TopP float64 `json:"top_p,omitempty"`
30
+ RequestId string `json:"request_id,omitempty"`
31
+ Incremental bool `json:"incremental,omitempty"`
32
+ }
33
+
34
+ type ZhipuResponseData struct {
35
+ TaskId string `json:"task_id"`
36
+ RequestId string `json:"request_id"`
37
+ TaskStatus string `json:"task_status"`
38
+ Choices []ZhipuMessage `json:"choices"`
39
+ Usage `json:"usage"`
40
+ }
41
+
42
+ type ZhipuResponse struct {
43
+ Code int `json:"code"`
44
+ Msg string `json:"msg"`
45
+ Success bool `json:"success"`
46
+ Data ZhipuResponseData `json:"data"`
47
+ }
48
+
49
+ type ZhipuStreamMetaResponse struct {
50
+ RequestId string `json:"request_id"`
51
+ TaskId string `json:"task_id"`
52
+ TaskStatus string `json:"task_status"`
53
+ Usage `json:"usage"`
54
+ }
55
+
56
+ type zhipuTokenData struct {
57
+ Token string
58
+ ExpiryTime time.Time
59
+ }
60
+
61
+ var zhipuTokens sync.Map
62
+ var expSeconds int64 = 24 * 3600
63
+
64
+ func getZhipuToken(apikey string) string {
65
+ data, ok := zhipuTokens.Load(apikey)
66
+ if ok {
67
+ tokenData := data.(zhipuTokenData)
68
+ if time.Now().Before(tokenData.ExpiryTime) {
69
+ return tokenData.Token
70
+ }
71
+ }
72
+
73
+ split := strings.Split(apikey, ".")
74
+ if len(split) != 2 {
75
+ common.SysError("invalid zhipu key: " + apikey)
76
+ return ""
77
+ }
78
+
79
+ id := split[0]
80
+ secret := split[1]
81
+
82
+ expMillis := time.Now().Add(time.Duration(expSeconds)*time.Second).UnixNano() / 1e6
83
+ expiryTime := time.Now().Add(time.Duration(expSeconds) * time.Second)
84
+
85
+ timestamp := time.Now().UnixNano() / 1e6
86
+
87
+ payload := jwt.MapClaims{
88
+ "api_key": id,
89
+ "exp": expMillis,
90
+ "timestamp": timestamp,
91
+ }
92
+
93
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
94
+
95
+ token.Header["alg"] = "HS256"
96
+ token.Header["sign_type"] = "SIGN"
97
+
98
+ tokenString, err := token.SignedString([]byte(secret))
99
+ if err != nil {
100
+ return ""
101
+ }
102
+
103
+ zhipuTokens.Store(apikey, zhipuTokenData{
104
+ Token: tokenString,
105
+ ExpiryTime: expiryTime,
106
+ })
107
+
108
+ return tokenString
109
+ }
110
+
111
+ func requestOpenAI2Zhipu(request GeneralOpenAIRequest) *ZhipuRequest {
112
+ messages := make([]ZhipuMessage, 0, len(request.Messages))
113
+ for _, message := range request.Messages {
114
+ if message.Role == "system" {
115
+ messages = append(messages, ZhipuMessage{
116
+ Role: "system",
117
+ Content: message.Content,
118
+ })
119
+ messages = append(messages, ZhipuMessage{
120
+ Role: "user",
121
+ Content: "Okay",
122
+ })
123
+ } else {
124
+ messages = append(messages, ZhipuMessage{
125
+ Role: message.Role,
126
+ Content: message.Content,
127
+ })
128
+ }
129
+ }
130
+ return &ZhipuRequest{
131
+ Prompt: messages,
132
+ Temperature: request.Temperature,
133
+ TopP: request.TopP,
134
+ Incremental: false,
135
+ }
136
+ }
137
+
138
+ func responseZhipu2OpenAI(response *ZhipuResponse) *OpenAITextResponse {
139
+ fullTextResponse := OpenAITextResponse{
140
+ Id: response.Data.TaskId,
141
+ Object: "chat.completion",
142
+ Created: common.GetTimestamp(),
143
+ Choices: make([]OpenAITextResponseChoice, 0, len(response.Data.Choices)),
144
+ Usage: response.Data.Usage,
145
+ }
146
+ for i, choice := range response.Data.Choices {
147
+ openaiChoice := OpenAITextResponseChoice{
148
+ Index: i,
149
+ Message: Message{
150
+ Role: choice.Role,
151
+ Content: strings.Trim(choice.Content, "\""),
152
+ },
153
+ FinishReason: "",
154
+ }
155
+ if i == len(response.Data.Choices)-1 {
156
+ openaiChoice.FinishReason = "stop"
157
+ }
158
+ fullTextResponse.Choices = append(fullTextResponse.Choices, openaiChoice)
159
+ }
160
+ return &fullTextResponse
161
+ }
162
+
163
+ func streamResponseZhipu2OpenAI(zhipuResponse string) *ChatCompletionsStreamResponse {
164
+ var choice ChatCompletionsStreamResponseChoice
165
+ choice.Delta.Content = zhipuResponse
166
+ response := ChatCompletionsStreamResponse{
167
+ Object: "chat.completion.chunk",
168
+ Created: common.GetTimestamp(),
169
+ Model: "chatglm",
170
+ Choices: []ChatCompletionsStreamResponseChoice{choice},
171
+ }
172
+ return &response
173
+ }
174
+
175
+ func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*ChatCompletionsStreamResponse, *Usage) {
176
+ var choice ChatCompletionsStreamResponseChoice
177
+ choice.Delta.Content = ""
178
+ choice.FinishReason = &stopFinishReason
179
+ response := ChatCompletionsStreamResponse{
180
+ Id: zhipuResponse.RequestId,
181
+ Object: "chat.completion.chunk",
182
+ Created: common.GetTimestamp(),
183
+ Model: "chatglm",
184
+ Choices: []ChatCompletionsStreamResponseChoice{choice},
185
+ }
186
+ return &response, &zhipuResponse.Usage
187
+ }
188
+
189
+ func zhipuStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
190
+ var usage *Usage
191
+ scanner := bufio.NewScanner(resp.Body)
192
+ scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
193
+ if atEOF && len(data) == 0 {
194
+ return 0, nil, nil
195
+ }
196
+ if i := strings.Index(string(data), "\n\n"); i >= 0 && strings.Index(string(data), ":") >= 0 {
197
+ return i + 2, data[0:i], nil
198
+ }
199
+ if atEOF {
200
+ return len(data), data, nil
201
+ }
202
+ return 0, nil, nil
203
+ })
204
+ dataChan := make(chan string)
205
+ metaChan := make(chan string)
206
+ stopChan := make(chan bool)
207
+ go func() {
208
+ for scanner.Scan() {
209
+ data := scanner.Text()
210
+ lines := strings.Split(data, "\n")
211
+ for i, line := range lines {
212
+ if len(line) < 5 {
213
+ continue
214
+ }
215
+ if line[:5] == "data:" {
216
+ dataChan <- line[5:]
217
+ if i != len(lines)-1 {
218
+ dataChan <- "\n"
219
+ }
220
+ } else if line[:5] == "meta:" {
221
+ metaChan <- line[5:]
222
+ }
223
+ }
224
+ }
225
+ stopChan <- true
226
+ }()
227
+ setEventStreamHeaders(c)
228
+ c.Stream(func(w io.Writer) bool {
229
+ select {
230
+ case data := <-dataChan:
231
+ response := streamResponseZhipu2OpenAI(data)
232
+ jsonResponse, err := json.Marshal(response)
233
+ if err != nil {
234
+ common.SysError("error marshalling stream response: " + err.Error())
235
+ return true
236
+ }
237
+ c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)})
238
+ return true
239
+ case data := <-metaChan:
240
+ var zhipuResponse ZhipuStreamMetaResponse
241
+ err := json.Unmarshal([]byte(data), &zhipuResponse)
242
+ if err != nil {
243
+ common.SysError("error unmarshalling stream response: " + err.Error())
244
+ return true
245
+ }
246
+ response, zhipuUsage := streamMetaResponseZhipu2OpenAI(&zhipuResponse)
247
+ jsonResponse, err := json.Marshal(response)
248
+ if err != nil {
249
+ common.SysError("error marshalling stream response: " + err.Error())
250
+ return true
251
+ }
252
+ usage = zhipuUsage
253
+ c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)})
254
+ return true
255
+ case <-stopChan:
256
+ c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
257
+ return false
258
+ }
259
+ })
260
+ err := resp.Body.Close()
261
+ if err != nil {
262
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
263
+ }
264
+ return nil, usage
265
+ }
266
+
267
+ func zhipuHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) {
268
+ var zhipuResponse ZhipuResponse
269
+ responseBody, err := io.ReadAll(resp.Body)
270
+ if err != nil {
271
+ return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
272
+ }
273
+ err = resp.Body.Close()
274
+ if err != nil {
275
+ return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
276
+ }
277
+ err = json.Unmarshal(responseBody, &zhipuResponse)
278
+ if err != nil {
279
+ return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
280
+ }
281
+ if !zhipuResponse.Success {
282
+ return &OpenAIErrorWithStatusCode{
283
+ OpenAIError: OpenAIError{
284
+ Message: zhipuResponse.Msg,
285
+ Type: "zhipu_error",
286
+ Param: "",
287
+ Code: zhipuResponse.Code,
288
+ },
289
+ StatusCode: resp.StatusCode,
290
+ }, nil
291
+ }
292
+ fullTextResponse := responseZhipu2OpenAI(&zhipuResponse)
293
+ jsonResponse, err := json.Marshal(fullTextResponse)
294
+ if err != nil {
295
+ return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
296
+ }
297
+ c.Writer.Header().Set("Content-Type", "application/json")
298
+ c.Writer.WriteHeader(resp.StatusCode)
299
+ _, err = c.Writer.Write(jsonResponse)
300
+ return nil, &fullTextResponse.Usage
301
+ }
controller/relay.go ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "fmt"
5
+ "net/http"
6
+ "one-api/common"
7
+ "strconv"
8
+ "strings"
9
+
10
+ "github.com/gin-gonic/gin"
11
+ )
12
+
13
+ type Message struct {
14
+ Role string `json:"role"`
15
+ Content string `json:"content"`
16
+ Name *string `json:"name,omitempty"`
17
+ }
18
+
19
+ const (
20
+ RelayModeUnknown = iota
21
+ RelayModeChatCompletions
22
+ RelayModeCompletions
23
+ RelayModeEmbeddings
24
+ RelayModeModerations
25
+ RelayModeImagesGenerations
26
+ RelayModeEdits
27
+ RelayModeAudio
28
+ )
29
+
30
+ // https://platform.openai.com/docs/api-reference/chat
31
+
32
+ type GeneralOpenAIRequest struct {
33
+ Model string `json:"model,omitempty"`
34
+ Messages []Message `json:"messages,omitempty"`
35
+ Prompt any `json:"prompt,omitempty"`
36
+ Stream bool `json:"stream,omitempty"`
37
+ MaxTokens int `json:"max_tokens,omitempty"`
38
+ Temperature float64 `json:"temperature,omitempty"`
39
+ TopP float64 `json:"top_p,omitempty"`
40
+ N int `json:"n,omitempty"`
41
+ Input any `json:"input,omitempty"`
42
+ Instruction string `json:"instruction,omitempty"`
43
+ Size string `json:"size,omitempty"`
44
+ Functions any `json:"functions,omitempty"`
45
+ }
46
+
47
+ type ChatRequest struct {
48
+ Model string `json:"model"`
49
+ Messages []Message `json:"messages"`
50
+ MaxTokens int `json:"max_tokens"`
51
+ }
52
+
53
+ type TextRequest struct {
54
+ Model string `json:"model"`
55
+ Messages []Message `json:"messages"`
56
+ Prompt string `json:"prompt"`
57
+ MaxTokens int `json:"max_tokens"`
58
+ //Stream bool `json:"stream"`
59
+ }
60
+
61
+ type ImageRequest struct {
62
+ Prompt string `json:"prompt"`
63
+ N int `json:"n"`
64
+ Size string `json:"size"`
65
+ }
66
+
67
+ type AudioResponse struct {
68
+ Text string `json:"text,omitempty"`
69
+ }
70
+
71
+ type Usage struct {
72
+ PromptTokens int `json:"prompt_tokens"`
73
+ CompletionTokens int `json:"completion_tokens"`
74
+ TotalTokens int `json:"total_tokens"`
75
+ }
76
+
77
+ type OpenAIError struct {
78
+ Message string `json:"message"`
79
+ Type string `json:"type"`
80
+ Param string `json:"param"`
81
+ Code any `json:"code"`
82
+ }
83
+
84
+ type OpenAIErrorWithStatusCode struct {
85
+ OpenAIError
86
+ StatusCode int `json:"status_code"`
87
+ }
88
+
89
+ type TextResponse struct {
90
+ Choices []OpenAITextResponseChoice `json:"choices"`
91
+ Usage `json:"usage"`
92
+ Error OpenAIError `json:"error"`
93
+ }
94
+
95
+ type OpenAITextResponseChoice struct {
96
+ Index int `json:"index"`
97
+ Message `json:"message"`
98
+ FinishReason string `json:"finish_reason"`
99
+ }
100
+
101
+ type OpenAITextResponse struct {
102
+ Id string `json:"id"`
103
+ Object string `json:"object"`
104
+ Created int64 `json:"created"`
105
+ Choices []OpenAITextResponseChoice `json:"choices"`
106
+ Usage `json:"usage"`
107
+ }
108
+
109
+ type OpenAIEmbeddingResponseItem struct {
110
+ Object string `json:"object"`
111
+ Index int `json:"index"`
112
+ Embedding []float64 `json:"embedding"`
113
+ }
114
+
115
+ type OpenAIEmbeddingResponse struct {
116
+ Object string `json:"object"`
117
+ Data []OpenAIEmbeddingResponseItem `json:"data"`
118
+ Model string `json:"model"`
119
+ Usage `json:"usage"`
120
+ }
121
+
122
+ type ImageResponse struct {
123
+ Created int `json:"created"`
124
+ Data []struct {
125
+ Url string `json:"url"`
126
+ }
127
+ }
128
+
129
+ type ChatCompletionsStreamResponseChoice struct {
130
+ Delta struct {
131
+ Content string `json:"content"`
132
+ } `json:"delta"`
133
+ FinishReason *string `json:"finish_reason"`
134
+ }
135
+
136
+ type ChatCompletionsStreamResponse struct {
137
+ Id string `json:"id"`
138
+ Object string `json:"object"`
139
+ Created int64 `json:"created"`
140
+ Model string `json:"model"`
141
+ Choices []ChatCompletionsStreamResponseChoice `json:"choices"`
142
+ }
143
+
144
+ type CompletionsStreamResponse struct {
145
+ Choices []struct {
146
+ Text string `json:"text"`
147
+ FinishReason string `json:"finish_reason"`
148
+ } `json:"choices"`
149
+ }
150
+
151
+ func Relay(c *gin.Context) {
152
+ relayMode := RelayModeUnknown
153
+ if strings.HasPrefix(c.Request.URL.Path, "/v1/chat/completions") {
154
+ relayMode = RelayModeChatCompletions
155
+ } else if strings.HasPrefix(c.Request.URL.Path, "/v1/completions") {
156
+ relayMode = RelayModeCompletions
157
+ } else if strings.HasPrefix(c.Request.URL.Path, "/v1/embeddings") {
158
+ relayMode = RelayModeEmbeddings
159
+ } else if strings.HasSuffix(c.Request.URL.Path, "embeddings") {
160
+ relayMode = RelayModeEmbeddings
161
+ } else if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") {
162
+ relayMode = RelayModeModerations
163
+ } else if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") {
164
+ relayMode = RelayModeImagesGenerations
165
+ } else if strings.HasPrefix(c.Request.URL.Path, "/v1/edits") {
166
+ relayMode = RelayModeEdits
167
+ } else if strings.HasPrefix(c.Request.URL.Path, "/v1/audio") {
168
+ relayMode = RelayModeAudio
169
+ }
170
+ var err *OpenAIErrorWithStatusCode
171
+ switch relayMode {
172
+ case RelayModeImagesGenerations:
173
+ err = relayImageHelper(c, relayMode)
174
+ case RelayModeAudio:
175
+ err = relayAudioHelper(c, relayMode)
176
+ default:
177
+ err = relayTextHelper(c, relayMode)
178
+ }
179
+ if err != nil {
180
+ retryTimesStr := c.Query("retry")
181
+ retryTimes, _ := strconv.Atoi(retryTimesStr)
182
+ if retryTimesStr == "" {
183
+ retryTimes = common.RetryTimes
184
+ }
185
+ if retryTimes > 0 {
186
+ c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s?retry=%d", c.Request.URL.Path, retryTimes-1))
187
+ } else {
188
+ if err.StatusCode == http.StatusTooManyRequests {
189
+ err.OpenAIError.Message = "当前分组上游负载已饱和,请稍后再试"
190
+ }
191
+ c.JSON(err.StatusCode, gin.H{
192
+ "error": err.OpenAIError,
193
+ })
194
+ }
195
+ channelId := c.GetInt("channel_id")
196
+ common.SysError(fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Message))
197
+ // https://platform.openai.com/docs/guides/error-codes/api-errors
198
+ if shouldDisableChannel(&err.OpenAIError, err.StatusCode) {
199
+ channelId := c.GetInt("channel_id")
200
+ channelName := c.GetString("channel_name")
201
+ disableChannel(channelId, channelName, err.Message)
202
+ }
203
+ }
204
+ }
205
+
206
+ func RelayNotImplemented(c *gin.Context) {
207
+ err := OpenAIError{
208
+ Message: "API not implemented",
209
+ Type: "one_api_error",
210
+ Param: "",
211
+ Code: "api_not_implemented",
212
+ }
213
+ c.JSON(http.StatusNotImplemented, gin.H{
214
+ "error": err,
215
+ })
216
+ }
217
+
218
+ func RelayNotFound(c *gin.Context) {
219
+ err := OpenAIError{
220
+ Message: fmt.Sprintf("Invalid URL (%s %s)", c.Request.Method, c.Request.URL.Path),
221
+ Type: "invalid_request_error",
222
+ Param: "",
223
+ Code: "",
224
+ }
225
+ c.JSON(http.StatusNotFound, gin.H{
226
+ "error": err,
227
+ })
228
+ }
controller/token.go ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+ "net/http"
6
+ "one-api/common"
7
+ "one-api/model"
8
+ "strconv"
9
+ )
10
+
11
+ func GetAllTokens(c *gin.Context) {
12
+ userId := c.GetInt("id")
13
+ p, _ := strconv.Atoi(c.Query("p"))
14
+ if p < 0 {
15
+ p = 0
16
+ }
17
+ tokens, err := model.GetAllUserTokens(userId, p*common.ItemsPerPage, common.ItemsPerPage)
18
+ if err != nil {
19
+ c.JSON(http.StatusOK, gin.H{
20
+ "success": false,
21
+ "message": err.Error(),
22
+ })
23
+ return
24
+ }
25
+ c.JSON(http.StatusOK, gin.H{
26
+ "success": true,
27
+ "message": "",
28
+ "data": tokens,
29
+ })
30
+ return
31
+ }
32
+
33
+ func SearchTokens(c *gin.Context) {
34
+ userId := c.GetInt("id")
35
+ keyword := c.Query("keyword")
36
+ tokens, err := model.SearchUserTokens(userId, keyword)
37
+ if err != nil {
38
+ c.JSON(http.StatusOK, gin.H{
39
+ "success": false,
40
+ "message": err.Error(),
41
+ })
42
+ return
43
+ }
44
+ c.JSON(http.StatusOK, gin.H{
45
+ "success": true,
46
+ "message": "",
47
+ "data": tokens,
48
+ })
49
+ return
50
+ }
51
+
52
+ func GetToken(c *gin.Context) {
53
+ id, err := strconv.Atoi(c.Param("id"))
54
+ userId := c.GetInt("id")
55
+ if err != nil {
56
+ c.JSON(http.StatusOK, gin.H{
57
+ "success": false,
58
+ "message": err.Error(),
59
+ })
60
+ return
61
+ }
62
+ token, err := model.GetTokenByIds(id, userId)
63
+ if err != nil {
64
+ c.JSON(http.StatusOK, gin.H{
65
+ "success": false,
66
+ "message": err.Error(),
67
+ })
68
+ return
69
+ }
70
+ c.JSON(http.StatusOK, gin.H{
71
+ "success": true,
72
+ "message": "",
73
+ "data": token,
74
+ })
75
+ return
76
+ }
77
+
78
+ func GetTokenStatus(c *gin.Context) {
79
+ tokenId := c.GetInt("token_id")
80
+ userId := c.GetInt("id")
81
+ token, err := model.GetTokenByIds(tokenId, userId)
82
+ if err != nil {
83
+ c.JSON(http.StatusOK, gin.H{
84
+ "success": false,
85
+ "message": err.Error(),
86
+ })
87
+ return
88
+ }
89
+ expiredAt := token.ExpiredTime
90
+ if expiredAt == -1 {
91
+ expiredAt = 0
92
+ }
93
+ c.JSON(http.StatusOK, gin.H{
94
+ "object": "credit_summary",
95
+ "total_granted": token.RemainQuota,
96
+ "total_used": 0, // not supported currently
97
+ "total_available": token.RemainQuota,
98
+ "expires_at": expiredAt * 1000,
99
+ })
100
+ }
101
+
102
+ func AddToken(c *gin.Context) {
103
+ token := model.Token{}
104
+ err := c.ShouldBindJSON(&token)
105
+ if err != nil {
106
+ c.JSON(http.StatusOK, gin.H{
107
+ "success": false,
108
+ "message": err.Error(),
109
+ })
110
+ return
111
+ }
112
+ if len(token.Name) > 30 {
113
+ c.JSON(http.StatusOK, gin.H{
114
+ "success": false,
115
+ "message": "令牌名称过长",
116
+ })
117
+ return
118
+ }
119
+ cleanToken := model.Token{
120
+ UserId: c.GetInt("id"),
121
+ Name: token.Name,
122
+ Key: common.GenerateKey(),
123
+ CreatedTime: common.GetTimestamp(),
124
+ AccessedTime: common.GetTimestamp(),
125
+ ExpiredTime: token.ExpiredTime,
126
+ RemainQuota: token.RemainQuota,
127
+ UnlimitedQuota: token.UnlimitedQuota,
128
+ }
129
+ err = cleanToken.Insert()
130
+ if err != nil {
131
+ c.JSON(http.StatusOK, gin.H{
132
+ "success": false,
133
+ "message": err.Error(),
134
+ })
135
+ return
136
+ }
137
+ c.JSON(http.StatusOK, gin.H{
138
+ "success": true,
139
+ "message": "",
140
+ })
141
+ return
142
+ }
143
+
144
+ func DeleteToken(c *gin.Context) {
145
+ id, _ := strconv.Atoi(c.Param("id"))
146
+ userId := c.GetInt("id")
147
+ err := model.DeleteTokenById(id, userId)
148
+ if err != nil {
149
+ c.JSON(http.StatusOK, gin.H{
150
+ "success": false,
151
+ "message": err.Error(),
152
+ })
153
+ return
154
+ }
155
+ c.JSON(http.StatusOK, gin.H{
156
+ "success": true,
157
+ "message": "",
158
+ })
159
+ return
160
+ }
161
+
162
+ func UpdateToken(c *gin.Context) {
163
+ userId := c.GetInt("id")
164
+ statusOnly := c.Query("status_only")
165
+ token := model.Token{}
166
+ err := c.ShouldBindJSON(&token)
167
+ if err != nil {
168
+ c.JSON(http.StatusOK, gin.H{
169
+ "success": false,
170
+ "message": err.Error(),
171
+ })
172
+ return
173
+ }
174
+ if len(token.Name) > 30 {
175
+ c.JSON(http.StatusOK, gin.H{
176
+ "success": false,
177
+ "message": "令牌名称过长",
178
+ })
179
+ return
180
+ }
181
+ cleanToken, err := model.GetTokenByIds(token.Id, userId)
182
+ if err != nil {
183
+ c.JSON(http.StatusOK, gin.H{
184
+ "success": false,
185
+ "message": err.Error(),
186
+ })
187
+ return
188
+ }
189
+ if token.Status == common.TokenStatusEnabled {
190
+ if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() && cleanToken.ExpiredTime != -1 {
191
+ c.JSON(http.StatusOK, gin.H{
192
+ "success": false,
193
+ "message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期",
194
+ })
195
+ return
196
+ }
197
+ if cleanToken.Status == common.TokenStatusExhausted && cleanToken.RemainQuota <= 0 && !cleanToken.UnlimitedQuota {
198
+ c.JSON(http.StatusOK, gin.H{
199
+ "success": false,
200
+ "message": "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度",
201
+ })
202
+ return
203
+ }
204
+ }
205
+ if statusOnly != "" {
206
+ cleanToken.Status = token.Status
207
+ } else {
208
+ // If you add more fields, please also update token.Update()
209
+ cleanToken.Name = token.Name
210
+ cleanToken.ExpiredTime = token.ExpiredTime
211
+ cleanToken.RemainQuota = token.RemainQuota
212
+ cleanToken.UnlimitedQuota = token.UnlimitedQuota
213
+ }
214
+ err = cleanToken.Update()
215
+ if err != nil {
216
+ c.JSON(http.StatusOK, gin.H{
217
+ "success": false,
218
+ "message": err.Error(),
219
+ })
220
+ return
221
+ }
222
+ c.JSON(http.StatusOK, gin.H{
223
+ "success": true,
224
+ "message": "",
225
+ "data": cleanToken,
226
+ })
227
+ return
228
+ }
controller/user.go ADDED
@@ -0,0 +1,743 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "net/http"
7
+ "one-api/common"
8
+ "one-api/model"
9
+ "strconv"
10
+
11
+ "github.com/gin-contrib/sessions"
12
+ "github.com/gin-gonic/gin"
13
+ )
14
+
15
+ type LoginRequest struct {
16
+ Username string `json:"username"`
17
+ Password string `json:"password"`
18
+ }
19
+
20
+ func Login(c *gin.Context) {
21
+ if !common.PasswordLoginEnabled {
22
+ c.JSON(http.StatusOK, gin.H{
23
+ "message": "管理员关闭了密码登录",
24
+ "success": false,
25
+ })
26
+ return
27
+ }
28
+ var loginRequest LoginRequest
29
+ err := json.NewDecoder(c.Request.Body).Decode(&loginRequest)
30
+ if err != nil {
31
+ c.JSON(http.StatusOK, gin.H{
32
+ "message": "无效的参数",
33
+ "success": false,
34
+ })
35
+ return
36
+ }
37
+ username := loginRequest.Username
38
+ password := loginRequest.Password
39
+ if username == "" || password == "" {
40
+ c.JSON(http.StatusOK, gin.H{
41
+ "message": "无效的参数",
42
+ "success": false,
43
+ })
44
+ return
45
+ }
46
+ user := model.User{
47
+ Username: username,
48
+ Password: password,
49
+ }
50
+ err = user.ValidateAndFill()
51
+ if err != nil {
52
+ c.JSON(http.StatusOK, gin.H{
53
+ "message": err.Error(),
54
+ "success": false,
55
+ })
56
+ return
57
+ }
58
+ setupLogin(&user, c)
59
+ }
60
+
61
+ // setup session & cookies and then return user info
62
+ func setupLogin(user *model.User, c *gin.Context) {
63
+ session := sessions.Default(c)
64
+ session.Set("id", user.Id)
65
+ session.Set("username", user.Username)
66
+ session.Set("role", user.Role)
67
+ session.Set("status", user.Status)
68
+ err := session.Save()
69
+ if err != nil {
70
+ c.JSON(http.StatusOK, gin.H{
71
+ "message": "无法保存会话信息,请重试",
72
+ "success": false,
73
+ })
74
+ return
75
+ }
76
+ cleanUser := model.User{
77
+ Id: user.Id,
78
+ Username: user.Username,
79
+ DisplayName: user.DisplayName,
80
+ Role: user.Role,
81
+ Status: user.Status,
82
+ }
83
+ c.JSON(http.StatusOK, gin.H{
84
+ "message": "",
85
+ "success": true,
86
+ "data": cleanUser,
87
+ })
88
+ }
89
+
90
+ func Logout(c *gin.Context) {
91
+ session := sessions.Default(c)
92
+ session.Clear()
93
+ err := session.Save()
94
+ if err != nil {
95
+ c.JSON(http.StatusOK, gin.H{
96
+ "message": err.Error(),
97
+ "success": false,
98
+ })
99
+ return
100
+ }
101
+ c.JSON(http.StatusOK, gin.H{
102
+ "message": "",
103
+ "success": true,
104
+ })
105
+ }
106
+
107
+ func Register(c *gin.Context) {
108
+ if !common.RegisterEnabled {
109
+ c.JSON(http.StatusOK, gin.H{
110
+ "message": "管理员关闭了新用户注册",
111
+ "success": false,
112
+ })
113
+ return
114
+ }
115
+ if !common.PasswordRegisterEnabled {
116
+ c.JSON(http.StatusOK, gin.H{
117
+ "message": "管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册",
118
+ "success": false,
119
+ })
120
+ return
121
+ }
122
+ var user model.User
123
+ err := json.NewDecoder(c.Request.Body).Decode(&user)
124
+ if err != nil {
125
+ c.JSON(http.StatusOK, gin.H{
126
+ "success": false,
127
+ "message": "无效的参数",
128
+ })
129
+ return
130
+ }
131
+ if err := common.Validate.Struct(&user); err != nil {
132
+ c.JSON(http.StatusOK, gin.H{
133
+ "success": false,
134
+ "message": "输入不合法 " + err.Error(),
135
+ })
136
+ return
137
+ }
138
+ if common.EmailVerificationEnabled {
139
+ if user.Email == "" || user.VerificationCode == "" {
140
+ c.JSON(http.StatusOK, gin.H{
141
+ "success": false,
142
+ "message": "管理员开启了邮箱验证,请输入邮箱地址和验证码",
143
+ })
144
+ return
145
+ }
146
+ if !common.VerifyCodeWithKey(user.Email, user.VerificationCode, common.EmailVerificationPurpose) {
147
+ c.JSON(http.StatusOK, gin.H{
148
+ "success": false,
149
+ "message": "验证码错误或已过期",
150
+ })
151
+ return
152
+ }
153
+ }
154
+ affCode := user.AffCode // this code is the inviter's code, not the user's own code
155
+ inviterId, _ := model.GetUserIdByAffCode(affCode)
156
+ cleanUser := model.User{
157
+ Username: user.Username,
158
+ Password: user.Password,
159
+ DisplayName: user.Username,
160
+ InviterId: inviterId,
161
+ }
162
+ if common.EmailVerificationEnabled {
163
+ cleanUser.Email = user.Email
164
+ }
165
+ if err := cleanUser.Insert(inviterId); err != nil {
166
+ c.JSON(http.StatusOK, gin.H{
167
+ "success": false,
168
+ "message": err.Error(),
169
+ })
170
+ return
171
+ }
172
+ c.JSON(http.StatusOK, gin.H{
173
+ "success": true,
174
+ "message": "",
175
+ })
176
+ return
177
+ }
178
+
179
+ func GetAllUsers(c *gin.Context) {
180
+ p, _ := strconv.Atoi(c.Query("p"))
181
+ if p < 0 {
182
+ p = 0
183
+ }
184
+ users, err := model.GetAllUsers(p*common.ItemsPerPage, common.ItemsPerPage)
185
+ if err != nil {
186
+ c.JSON(http.StatusOK, gin.H{
187
+ "success": false,
188
+ "message": err.Error(),
189
+ })
190
+ return
191
+ }
192
+ c.JSON(http.StatusOK, gin.H{
193
+ "success": true,
194
+ "message": "",
195
+ "data": users,
196
+ })
197
+ return
198
+ }
199
+
200
+ func SearchUsers(c *gin.Context) {
201
+ keyword := c.Query("keyword")
202
+ users, err := model.SearchUsers(keyword)
203
+ if err != nil {
204
+ c.JSON(http.StatusOK, gin.H{
205
+ "success": false,
206
+ "message": err.Error(),
207
+ })
208
+ return
209
+ }
210
+ c.JSON(http.StatusOK, gin.H{
211
+ "success": true,
212
+ "message": "",
213
+ "data": users,
214
+ })
215
+ return
216
+ }
217
+
218
+ func GetUser(c *gin.Context) {
219
+ id, err := strconv.Atoi(c.Param("id"))
220
+ if err != nil {
221
+ c.JSON(http.StatusOK, gin.H{
222
+ "success": false,
223
+ "message": err.Error(),
224
+ })
225
+ return
226
+ }
227
+ user, err := model.GetUserById(id, false)
228
+ if err != nil {
229
+ c.JSON(http.StatusOK, gin.H{
230
+ "success": false,
231
+ "message": err.Error(),
232
+ })
233
+ return
234
+ }
235
+ myRole := c.GetInt("role")
236
+ if myRole <= user.Role && myRole != common.RoleRootUser {
237
+ c.JSON(http.StatusOK, gin.H{
238
+ "success": false,
239
+ "message": "无权获取同级或更高等级用户的信息",
240
+ })
241
+ return
242
+ }
243
+ c.JSON(http.StatusOK, gin.H{
244
+ "success": true,
245
+ "message": "",
246
+ "data": user,
247
+ })
248
+ return
249
+ }
250
+
251
+ func GenerateAccessToken(c *gin.Context) {
252
+ id := c.GetInt("id")
253
+ user, err := model.GetUserById(id, true)
254
+ if err != nil {
255
+ c.JSON(http.StatusOK, gin.H{
256
+ "success": false,
257
+ "message": err.Error(),
258
+ })
259
+ return
260
+ }
261
+ user.AccessToken = common.GetUUID()
262
+
263
+ if model.DB.Where("access_token = ?", user.AccessToken).First(user).RowsAffected != 0 {
264
+ c.JSON(http.StatusOK, gin.H{
265
+ "success": false,
266
+ "message": "请重试,系统生成的 UUID 竟然重复了!",
267
+ })
268
+ return
269
+ }
270
+
271
+ if err := user.Update(false); err != nil {
272
+ c.JSON(http.StatusOK, gin.H{
273
+ "success": false,
274
+ "message": err.Error(),
275
+ })
276
+ return
277
+ }
278
+
279
+ c.JSON(http.StatusOK, gin.H{
280
+ "success": true,
281
+ "message": "",
282
+ "data": user.AccessToken,
283
+ })
284
+ return
285
+ }
286
+
287
+ func GetAffCode(c *gin.Context) {
288
+ id := c.GetInt("id")
289
+ user, err := model.GetUserById(id, true)
290
+ if err != nil {
291
+ c.JSON(http.StatusOK, gin.H{
292
+ "success": false,
293
+ "message": err.Error(),
294
+ })
295
+ return
296
+ }
297
+ if user.AffCode == "" {
298
+ user.AffCode = common.GetRandomString(4)
299
+ if err := user.Update(false); err != nil {
300
+ c.JSON(http.StatusOK, gin.H{
301
+ "success": false,
302
+ "message": err.Error(),
303
+ })
304
+ return
305
+ }
306
+ }
307
+ c.JSON(http.StatusOK, gin.H{
308
+ "success": true,
309
+ "message": "",
310
+ "data": user.AffCode,
311
+ })
312
+ return
313
+ }
314
+
315
+ func GetSelf(c *gin.Context) {
316
+ id := c.GetInt("id")
317
+ user, err := model.GetUserById(id, false)
318
+ if err != nil {
319
+ c.JSON(http.StatusOK, gin.H{
320
+ "success": false,
321
+ "message": err.Error(),
322
+ })
323
+ return
324
+ }
325
+ c.JSON(http.StatusOK, gin.H{
326
+ "success": true,
327
+ "message": "",
328
+ "data": user,
329
+ })
330
+ return
331
+ }
332
+
333
+ func UpdateUser(c *gin.Context) {
334
+ var updatedUser model.User
335
+ err := json.NewDecoder(c.Request.Body).Decode(&updatedUser)
336
+ if err != nil || updatedUser.Id == 0 {
337
+ c.JSON(http.StatusOK, gin.H{
338
+ "success": false,
339
+ "message": "无效的参数",
340
+ })
341
+ return
342
+ }
343
+ if updatedUser.Password == "" {
344
+ updatedUser.Password = "$I_LOVE_U" // make Validator happy :)
345
+ }
346
+ if err := common.Validate.Struct(&updatedUser); err != nil {
347
+ c.JSON(http.StatusOK, gin.H{
348
+ "success": false,
349
+ "message": "输入不合法 " + err.Error(),
350
+ })
351
+ return
352
+ }
353
+ originUser, err := model.GetUserById(updatedUser.Id, false)
354
+ if err != nil {
355
+ c.JSON(http.StatusOK, gin.H{
356
+ "success": false,
357
+ "message": err.Error(),
358
+ })
359
+ return
360
+ }
361
+ myRole := c.GetInt("role")
362
+ if myRole <= originUser.Role && myRole != common.RoleRootUser {
363
+ c.JSON(http.StatusOK, gin.H{
364
+ "success": false,
365
+ "message": "无权更新同权限等级或更高权限等级的用户信息",
366
+ })
367
+ return
368
+ }
369
+ if myRole <= updatedUser.Role && myRole != common.RoleRootUser {
370
+ c.JSON(http.StatusOK, gin.H{
371
+ "success": false,
372
+ "message": "无权将其他用户权限等级提升到大于等于自己的权限等级",
373
+ })
374
+ return
375
+ }
376
+ if updatedUser.Password == "$I_LOVE_U" {
377
+ updatedUser.Password = "" // rollback to what it should be
378
+ }
379
+ updatePassword := updatedUser.Password != ""
380
+ if err := updatedUser.Update(updatePassword); err != nil {
381
+ c.JSON(http.StatusOK, gin.H{
382
+ "success": false,
383
+ "message": err.Error(),
384
+ })
385
+ return
386
+ }
387
+ if originUser.Quota != updatedUser.Quota {
388
+ model.RecordLog(originUser.Id, model.LogTypeManage, fmt.Sprintf("管理员将用户额度从 %s修改为 %s", common.LogQuota(originUser.Quota), common.LogQuota(updatedUser.Quota)))
389
+ }
390
+ c.JSON(http.StatusOK, gin.H{
391
+ "success": true,
392
+ "message": "",
393
+ })
394
+ return
395
+ }
396
+
397
+ func UpdateSelf(c *gin.Context) {
398
+ var user model.User
399
+ err := json.NewDecoder(c.Request.Body).Decode(&user)
400
+ if err != nil {
401
+ c.JSON(http.StatusOK, gin.H{
402
+ "success": false,
403
+ "message": "无效的参数",
404
+ })
405
+ return
406
+ }
407
+ if user.Password == "" {
408
+ user.Password = "$I_LOVE_U" // make Validator happy :)
409
+ }
410
+ if err := common.Validate.Struct(&user); err != nil {
411
+ c.JSON(http.StatusOK, gin.H{
412
+ "success": false,
413
+ "message": "输入不合法 " + err.Error(),
414
+ })
415
+ return
416
+ }
417
+
418
+ cleanUser := model.User{
419
+ Id: c.GetInt("id"),
420
+ Username: user.Username,
421
+ Password: user.Password,
422
+ DisplayName: user.DisplayName,
423
+ }
424
+ if user.Password == "$I_LOVE_U" {
425
+ user.Password = "" // rollback to what it should be
426
+ cleanUser.Password = ""
427
+ }
428
+ updatePassword := user.Password != ""
429
+ if err := cleanUser.Update(updatePassword); err != nil {
430
+ c.JSON(http.StatusOK, gin.H{
431
+ "success": false,
432
+ "message": err.Error(),
433
+ })
434
+ return
435
+ }
436
+
437
+ c.JSON(http.StatusOK, gin.H{
438
+ "success": true,
439
+ "message": "",
440
+ })
441
+ return
442
+ }
443
+
444
+ func DeleteUser(c *gin.Context) {
445
+ id, err := strconv.Atoi(c.Param("id"))
446
+ if err != nil {
447
+ c.JSON(http.StatusOK, gin.H{
448
+ "success": false,
449
+ "message": err.Error(),
450
+ })
451
+ return
452
+ }
453
+ originUser, err := model.GetUserById(id, false)
454
+ if err != nil {
455
+ c.JSON(http.StatusOK, gin.H{
456
+ "success": false,
457
+ "message": err.Error(),
458
+ })
459
+ return
460
+ }
461
+ myRole := c.GetInt("role")
462
+ if myRole <= originUser.Role {
463
+ c.JSON(http.StatusOK, gin.H{
464
+ "success": false,
465
+ "message": "无权删除同权限等级或更高权限等级的用户",
466
+ })
467
+ return
468
+ }
469
+ err = model.DeleteUserById(id)
470
+ if err != nil {
471
+ c.JSON(http.StatusOK, gin.H{
472
+ "success": true,
473
+ "message": "",
474
+ })
475
+ return
476
+ }
477
+ }
478
+
479
+ func DeleteSelf(c *gin.Context) {
480
+ id := c.GetInt("id")
481
+ user, _ := model.GetUserById(id, false)
482
+
483
+ if user.Role == common.RoleRootUser {
484
+ c.JSON(http.StatusOK, gin.H{
485
+ "success": false,
486
+ "message": "不能删除超级管理员账户",
487
+ })
488
+ return
489
+ }
490
+
491
+ err := model.DeleteUserById(id)
492
+ if err != nil {
493
+ c.JSON(http.StatusOK, gin.H{
494
+ "success": false,
495
+ "message": err.Error(),
496
+ })
497
+ return
498
+ }
499
+ c.JSON(http.StatusOK, gin.H{
500
+ "success": true,
501
+ "message": "",
502
+ })
503
+ return
504
+ }
505
+
506
+ func CreateUser(c *gin.Context) {
507
+ var user model.User
508
+ err := json.NewDecoder(c.Request.Body).Decode(&user)
509
+ if err != nil || user.Username == "" || user.Password == "" {
510
+ c.JSON(http.StatusOK, gin.H{
511
+ "success": false,
512
+ "message": "无效的参数",
513
+ })
514
+ return
515
+ }
516
+ if err := common.Validate.Struct(&user); err != nil {
517
+ c.JSON(http.StatusOK, gin.H{
518
+ "success": false,
519
+ "message": "输入不合法 " + err.Error(),
520
+ })
521
+ return
522
+ }
523
+ if user.DisplayName == "" {
524
+ user.DisplayName = user.Username
525
+ }
526
+ myRole := c.GetInt("role")
527
+ if user.Role >= myRole {
528
+ c.JSON(http.StatusOK, gin.H{
529
+ "success": false,
530
+ "message": "无法创建权限大于等于自己的用户",
531
+ })
532
+ return
533
+ }
534
+ // Even for admin users, we cannot fully trust them!
535
+ cleanUser := model.User{
536
+ Username: user.Username,
537
+ Password: user.Password,
538
+ DisplayName: user.DisplayName,
539
+ }
540
+ if err := cleanUser.Insert(0); err != nil {
541
+ c.JSON(http.StatusOK, gin.H{
542
+ "success": false,
543
+ "message": err.Error(),
544
+ })
545
+ return
546
+ }
547
+
548
+ c.JSON(http.StatusOK, gin.H{
549
+ "success": true,
550
+ "message": "",
551
+ })
552
+ return
553
+ }
554
+
555
+ type ManageRequest struct {
556
+ Username string `json:"username"`
557
+ Action string `json:"action"`
558
+ }
559
+
560
+ // ManageUser Only admin user can do this
561
+ func ManageUser(c *gin.Context) {
562
+ var req ManageRequest
563
+ err := json.NewDecoder(c.Request.Body).Decode(&req)
564
+
565
+ if err != nil {
566
+ c.JSON(http.StatusOK, gin.H{
567
+ "success": false,
568
+ "message": "无效的参数",
569
+ })
570
+ return
571
+ }
572
+ user := model.User{
573
+ Username: req.Username,
574
+ }
575
+ // Fill attributes
576
+ model.DB.Where(&user).First(&user)
577
+ if user.Id == 0 {
578
+ c.JSON(http.StatusOK, gin.H{
579
+ "success": false,
580
+ "message": "用户不存在",
581
+ })
582
+ return
583
+ }
584
+ myRole := c.GetInt("role")
585
+ if myRole <= user.Role && myRole != common.RoleRootUser {
586
+ c.JSON(http.StatusOK, gin.H{
587
+ "success": false,
588
+ "message": "无权更新同权限等级或更高权限等级的用户信息",
589
+ })
590
+ return
591
+ }
592
+ switch req.Action {
593
+ case "disable":
594
+ user.Status = common.UserStatusDisabled
595
+ if user.Role == common.RoleRootUser {
596
+ c.JSON(http.StatusOK, gin.H{
597
+ "success": false,
598
+ "message": "无法禁用超级管理员用户",
599
+ })
600
+ return
601
+ }
602
+ case "enable":
603
+ user.Status = common.UserStatusEnabled
604
+ case "delete":
605
+ if user.Role == common.RoleRootUser {
606
+ c.JSON(http.StatusOK, gin.H{
607
+ "success": false,
608
+ "message": "无法删除超级管理员用户",
609
+ })
610
+ return
611
+ }
612
+ if err := user.Delete(); err != nil {
613
+ c.JSON(http.StatusOK, gin.H{
614
+ "success": false,
615
+ "message": err.Error(),
616
+ })
617
+ return
618
+ }
619
+ case "promote":
620
+ if myRole != common.RoleRootUser {
621
+ c.JSON(http.StatusOK, gin.H{
622
+ "success": false,
623
+ "message": "普通管理员用户无法提升其他用户为管理员",
624
+ })
625
+ return
626
+ }
627
+ if user.Role >= common.RoleAdminUser {
628
+ c.JSON(http.StatusOK, gin.H{
629
+ "success": false,
630
+ "message": "该用户已经是管理员",
631
+ })
632
+ return
633
+ }
634
+ user.Role = common.RoleAdminUser
635
+ case "demote":
636
+ if user.Role == common.RoleRootUser {
637
+ c.JSON(http.StatusOK, gin.H{
638
+ "success": false,
639
+ "message": "无法降级超级管理员用户",
640
+ })
641
+ return
642
+ }
643
+ if user.Role == common.RoleCommonUser {
644
+ c.JSON(http.StatusOK, gin.H{
645
+ "success": false,
646
+ "message": "该用户已经是普通用户",
647
+ })
648
+ return
649
+ }
650
+ user.Role = common.RoleCommonUser
651
+ }
652
+
653
+ if err := user.Update(false); err != nil {
654
+ c.JSON(http.StatusOK, gin.H{
655
+ "success": false,
656
+ "message": err.Error(),
657
+ })
658
+ return
659
+ }
660
+ clearUser := model.User{
661
+ Role: user.Role,
662
+ Status: user.Status,
663
+ }
664
+ c.JSON(http.StatusOK, gin.H{
665
+ "success": true,
666
+ "message": "",
667
+ "data": clearUser,
668
+ })
669
+ return
670
+ }
671
+
672
+ func EmailBind(c *gin.Context) {
673
+ email := c.Query("email")
674
+ code := c.Query("code")
675
+ if !common.VerifyCodeWithKey(email, code, common.EmailVerificationPurpose) {
676
+ c.JSON(http.StatusOK, gin.H{
677
+ "success": false,
678
+ "message": "验证码错误或已过期",
679
+ })
680
+ return
681
+ }
682
+ id := c.GetInt("id")
683
+ user := model.User{
684
+ Id: id,
685
+ }
686
+ err := user.FillUserById()
687
+ if err != nil {
688
+ c.JSON(http.StatusOK, gin.H{
689
+ "success": false,
690
+ "message": err.Error(),
691
+ })
692
+ return
693
+ }
694
+ user.Email = email
695
+ // no need to check if this email already taken, because we have used verification code to check it
696
+ err = user.Update(false)
697
+ if err != nil {
698
+ c.JSON(http.StatusOK, gin.H{
699
+ "success": false,
700
+ "message": err.Error(),
701
+ })
702
+ return
703
+ }
704
+ if user.Role == common.RoleRootUser {
705
+ common.RootUserEmail = email
706
+ }
707
+ c.JSON(http.StatusOK, gin.H{
708
+ "success": true,
709
+ "message": "",
710
+ })
711
+ return
712
+ }
713
+
714
+ type topUpRequest struct {
715
+ Key string `json:"key"`
716
+ }
717
+
718
+ func TopUp(c *gin.Context) {
719
+ req := topUpRequest{}
720
+ err := c.ShouldBindJSON(&req)
721
+ if err != nil {
722
+ c.JSON(http.StatusOK, gin.H{
723
+ "success": false,
724
+ "message": err.Error(),
725
+ })
726
+ return
727
+ }
728
+ id := c.GetInt("id")
729
+ quota, err := model.Redeem(req.Key, id)
730
+ if err != nil {
731
+ c.JSON(http.StatusOK, gin.H{
732
+ "success": false,
733
+ "message": err.Error(),
734
+ })
735
+ return
736
+ }
737
+ c.JSON(http.StatusOK, gin.H{
738
+ "success": true,
739
+ "message": "",
740
+ "data": quota,
741
+ })
742
+ return
743
+ }
controller/wechat.go ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "fmt"
7
+ "github.com/gin-gonic/gin"
8
+ "net/http"
9
+ "one-api/common"
10
+ "one-api/model"
11
+ "strconv"
12
+ "time"
13
+ )
14
+
15
+ type wechatLoginResponse struct {
16
+ Success bool `json:"success"`
17
+ Message string `json:"message"`
18
+ Data string `json:"data"`
19
+ }
20
+
21
+ func getWeChatIdByCode(code string) (string, error) {
22
+ if code == "" {
23
+ return "", errors.New("无效的参数")
24
+ }
25
+ req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/wechat/user?code=%s", common.WeChatServerAddress, code), nil)
26
+ if err != nil {
27
+ return "", err
28
+ }
29
+ req.Header.Set("Authorization", common.WeChatServerToken)
30
+ client := http.Client{
31
+ Timeout: 5 * time.Second,
32
+ }
33
+ httpResponse, err := client.Do(req)
34
+ if err != nil {
35
+ return "", err
36
+ }
37
+ defer httpResponse.Body.Close()
38
+ var res wechatLoginResponse
39
+ err = json.NewDecoder(httpResponse.Body).Decode(&res)
40
+ if err != nil {
41
+ return "", err
42
+ }
43
+ if !res.Success {
44
+ return "", errors.New(res.Message)
45
+ }
46
+ if res.Data == "" {
47
+ return "", errors.New("验证码错误或已过期")
48
+ }
49
+ return res.Data, nil
50
+ }
51
+
52
+ func WeChatAuth(c *gin.Context) {
53
+ if !common.WeChatAuthEnabled {
54
+ c.JSON(http.StatusOK, gin.H{
55
+ "message": "管理员未开启通过微信登录以及注册",
56
+ "success": false,
57
+ })
58
+ return
59
+ }
60
+ code := c.Query("code")
61
+ wechatId, err := getWeChatIdByCode(code)
62
+ if err != nil {
63
+ c.JSON(http.StatusOK, gin.H{
64
+ "message": err.Error(),
65
+ "success": false,
66
+ })
67
+ return
68
+ }
69
+ user := model.User{
70
+ WeChatId: wechatId,
71
+ }
72
+ if model.IsWeChatIdAlreadyTaken(wechatId) {
73
+ err := user.FillUserByWeChatId()
74
+ if err != nil {
75
+ c.JSON(http.StatusOK, gin.H{
76
+ "success": false,
77
+ "message": err.Error(),
78
+ })
79
+ return
80
+ }
81
+ } else {
82
+ if common.RegisterEnabled {
83
+ user.Username = "wechat_" + strconv.Itoa(model.GetMaxUserId()+1)
84
+ user.DisplayName = "WeChat User"
85
+ user.Role = common.RoleCommonUser
86
+ user.Status = common.UserStatusEnabled
87
+
88
+ if err := user.Insert(0); err != nil {
89
+ c.JSON(http.StatusOK, gin.H{
90
+ "success": false,
91
+ "message": err.Error(),
92
+ })
93
+ return
94
+ }
95
+ } else {
96
+ c.JSON(http.StatusOK, gin.H{
97
+ "success": false,
98
+ "message": "管理员关闭了新用户注册",
99
+ })
100
+ return
101
+ }
102
+ }
103
+
104
+ if user.Status != common.UserStatusEnabled {
105
+ c.JSON(http.StatusOK, gin.H{
106
+ "message": "用户已被封禁",
107
+ "success": false,
108
+ })
109
+ return
110
+ }
111
+ setupLogin(&user, c)
112
+ }
113
+
114
+ func WeChatBind(c *gin.Context) {
115
+ if !common.WeChatAuthEnabled {
116
+ c.JSON(http.StatusOK, gin.H{
117
+ "message": "管理员未开启通过微信登录以及注册",
118
+ "success": false,
119
+ })
120
+ return
121
+ }
122
+ code := c.Query("code")
123
+ wechatId, err := getWeChatIdByCode(code)
124
+ if err != nil {
125
+ c.JSON(http.StatusOK, gin.H{
126
+ "message": err.Error(),
127
+ "success": false,
128
+ })
129
+ return
130
+ }
131
+ if model.IsWeChatIdAlreadyTaken(wechatId) {
132
+ c.JSON(http.StatusOK, gin.H{
133
+ "success": false,
134
+ "message": "该微信账号已被绑定",
135
+ })
136
+ return
137
+ }
138
+ id := c.GetInt("id")
139
+ user := model.User{
140
+ Id: id,
141
+ }
142
+ err = user.FillUserById()
143
+ if err != nil {
144
+ c.JSON(http.StatusOK, gin.H{
145
+ "success": false,
146
+ "message": err.Error(),
147
+ })
148
+ return
149
+ }
150
+ user.WeChatId = wechatId
151
+ err = user.Update(false)
152
+ if err != nil {
153
+ c.JSON(http.StatusOK, gin.H{
154
+ "success": false,
155
+ "message": err.Error(),
156
+ })
157
+ return
158
+ }
159
+ c.JSON(http.StatusOK, gin.H{
160
+ "success": true,
161
+ "message": "",
162
+ })
163
+ return
164
+ }
docker-compose.yml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.4'
2
+
3
+ services:
4
+ one-api:
5
+ image: justsong/one-api:latest
6
+ container_name: one-api
7
+ restart: always
8
+ command: --log-dir /app/logs
9
+ ports:
10
+ - "3000:3000"
11
+ volumes:
12
+ - ./data:/data
13
+ - ./logs:/app/logs
14
+ environment:
15
+ - SQL_DSN=root:123456@tcp(host.docker.internal:3306)/one-api # 修改此行,或注释掉以使用 SQLite 作为数据库
16
+ - REDIS_CONN_STRING=redis://redis
17
+ - SESSION_SECRET=random_string # 修改为随机字符串
18
+ - TZ=Asia/Shanghai
19
+ # - NODE_TYPE=slave # 多机部署时从节点取消注释该行
20
+ # - SYNC_FREQUENCY=60 # 需要定期从数据库加载数据时取消注释该行
21
+ # - FRONTEND_BASE_URL=https://openai.justsong.cn # 多机部署时从节点取消注释该行
22
+
23
+ depends_on:
24
+ - redis
25
+ healthcheck:
26
+ test: [ "CMD-SHELL", "curl -s http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk '{print $2}' | grep 'true'" ]
27
+ interval: 30s
28
+ timeout: 10s
29
+ retries: 3
30
+
31
+ redis:
32
+ image: redis:latest
33
+ container_name: redis
34
+ restart: always
go.mod ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module one-api
2
+
3
+ // +heroku goVersion go1.18
4
+ go 1.18
5
+
6
+ require (
7
+ github.com/gin-contrib/cors v1.4.0
8
+ github.com/gin-contrib/gzip v0.0.6
9
+ github.com/gin-contrib/sessions v0.0.5
10
+ github.com/gin-contrib/static v0.0.1
11
+ github.com/gin-gonic/gin v1.9.1
12
+ github.com/go-playground/validator/v10 v10.14.0
13
+ github.com/go-redis/redis/v8 v8.11.5
14
+ github.com/golang-jwt/jwt v3.2.2+incompatible
15
+ github.com/google/uuid v1.3.0
16
+ github.com/gorilla/websocket v1.5.0
17
+ github.com/pkoukk/tiktoken-go v0.1.5
18
+ golang.org/x/crypto v0.9.0
19
+ gorm.io/driver/mysql v1.4.3
20
+ gorm.io/driver/sqlite v1.4.3
21
+ gorm.io/gorm v1.25.0
22
+ )
23
+
24
+ require (
25
+ github.com/bytedance/sonic v1.9.1 // indirect
26
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
27
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
28
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
29
+ github.com/dlclark/regexp2 v1.10.0 // indirect
30
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
31
+ github.com/gin-contrib/sse v0.1.0 // indirect
32
+ github.com/go-playground/locales v0.14.1 // indirect
33
+ github.com/go-playground/universal-translator v0.18.1 // indirect
34
+ github.com/go-sql-driver/mysql v1.6.0 // indirect
35
+ github.com/goccy/go-json v0.10.2 // indirect
36
+ github.com/gorilla/context v1.1.1 // indirect
37
+ github.com/gorilla/securecookie v1.1.1 // indirect
38
+ github.com/gorilla/sessions v1.2.1 // indirect
39
+ github.com/jackc/pgpassfile v1.0.0 // indirect
40
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
41
+ github.com/jackc/pgx/v5 v5.3.1 // indirect
42
+ github.com/jinzhu/inflection v1.0.0 // indirect
43
+ github.com/jinzhu/now v1.1.5 // indirect
44
+ github.com/json-iterator/go v1.1.12 // indirect
45
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
46
+ github.com/leodido/go-urn v1.2.4 // indirect
47
+ github.com/mattn/go-isatty v0.0.19 // indirect
48
+ github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
49
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
50
+ github.com/modern-go/reflect2 v1.0.2 // indirect
51
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
52
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
53
+ github.com/ugorji/go/codec v1.2.11 // indirect
54
+ golang.org/x/arch v0.3.0 // indirect
55
+ golang.org/x/net v0.10.0 // indirect
56
+ golang.org/x/sys v0.8.0 // indirect
57
+ golang.org/x/text v0.9.0 // indirect
58
+ google.golang.org/protobuf v1.30.0 // indirect
59
+ gopkg.in/yaml.v3 v3.0.1 // indirect
60
+ gorm.io/driver/postgres v1.5.2 // indirect
61
+ )
go.sum ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
2
+ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
3
+ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
4
+ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
5
+ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
6
+ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
7
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
8
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
9
+ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
10
+ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
12
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
14
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
15
+ github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
16
+ github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
17
+ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
18
+ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
19
+ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
20
+ github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
21
+ github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
22
+ github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
23
+ github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
24
+ github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
25
+ github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
26
+ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
27
+ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
28
+ github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
29
+ github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
30
+ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
31
+ github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
32
+ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
33
+ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
34
+ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
35
+ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
36
+ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
37
+ github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
38
+ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
39
+ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
40
+ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
41
+ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
42
+ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
43
+ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
44
+ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
45
+ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
46
+ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
47
+ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
48
+ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
49
+ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
50
+ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
51
+ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
52
+ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
53
+ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
54
+ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
55
+ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
56
+ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
57
+ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
58
+ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
59
+ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
60
+ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
61
+ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
62
+ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
63
+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
64
+ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
65
+ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
66
+ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
67
+ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
68
+ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
69
+ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
70
+ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
71
+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
72
+ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
73
+ github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
74
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
75
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
76
+ github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
77
+ github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
78
+ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
79
+ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
80
+ github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
81
+ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
82
+ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
83
+ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
84
+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
85
+ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
86
+ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
87
+ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
88
+ github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
89
+ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
90
+ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
91
+ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
92
+ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
93
+ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
94
+ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
95
+ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
96
+ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
97
+ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
98
+ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
99
+ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
100
+ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
101
+ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
102
+ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
103
+ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
104
+ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
105
+ github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
106
+ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
107
+ github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
108
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
109
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
110
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
111
+ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
112
+ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
113
+ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
114
+ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
115
+ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
116
+ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
117
+ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
118
+ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
119
+ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
120
+ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
121
+ github.com/pkoukk/tiktoken-go v0.1.5 h1:hAlT4dCf6Uk50x8E7HQrddhH3EWMKUN+LArExQQsQx4=
122
+ github.com/pkoukk/tiktoken-go v0.1.5/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
123
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
124
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
125
+ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
126
+ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
127
+ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
128
+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
129
+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
130
+ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
131
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
132
+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
133
+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
134
+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
135
+ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
136
+ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
137
+ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
138
+ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
139
+ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
140
+ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
141
+ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
142
+ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
143
+ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
144
+ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
145
+ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
146
+ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
147
+ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
148
+ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
149
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
150
+ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
151
+ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
152
+ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
153
+ golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
154
+ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
155
+ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
156
+ golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
157
+ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
158
+ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
159
+ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
160
+ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
161
+ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
162
+ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
163
+ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
164
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
165
+ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
166
+ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
167
+ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
168
+ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
169
+ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
170
+ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
171
+ golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
172
+ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
173
+ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
174
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
175
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
176
+ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
177
+ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
178
+ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
179
+ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
180
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
181
+ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
182
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
183
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
184
+ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
185
+ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
186
+ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
187
+ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
188
+ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
189
+ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
190
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
191
+ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
192
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
193
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
194
+ gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
195
+ gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
196
+ gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
197
+ gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
198
+ gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
199
+ gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
200
+ gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
201
+ gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=
202
+ gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
203
+ gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
204
+ gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
205
+ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=