Merge remote-tracking branch 'songquanpeng/main'
# Conflicts: # controller/relay.go
This commit is contained in:
commit
7c1a02d0dd
21
README.md
21
README.md
@ -29,10 +29,10 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/songquanpeng/one-api/releases">程序下载</a>
|
|
||||||
·
|
|
||||||
<a href="https://github.com/songquanpeng/one-api#部署">部署教程</a>
|
<a href="https://github.com/songquanpeng/one-api#部署">部署教程</a>
|
||||||
·
|
·
|
||||||
|
<a href="https://github.com/songquanpeng/one-api#使用方法">使用方法</a>
|
||||||
|
·
|
||||||
<a href="https://github.com/songquanpeng/one-api/issues">意见反馈</a>
|
<a href="https://github.com/songquanpeng/one-api/issues">意见反馈</a>
|
||||||
·
|
·
|
||||||
<a href="https://github.com/songquanpeng/one-api#截图展示">截图展示</a>
|
<a href="https://github.com/songquanpeng/one-api#截图展示">截图展示</a>
|
||||||
@ -158,11 +158,24 @@ sudo service nginx restart
|
|||||||
|
|
||||||
等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。
|
等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。
|
||||||
|
|
||||||
## 使用方式
|
## 使用方法
|
||||||
在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增一个访问令牌。
|
在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。
|
||||||
|
|
||||||
之后就可以使用你的令牌访问 One API 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。
|
之后就可以使用你的令牌访问 One API 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。
|
||||||
|
|
||||||
|
你需要在各种用到 OpenAI API 的地方设置 API Base 为你的 One API 的部署地址,例如:`https://openai.justsong.cn`,API Key 则为你在 One API 中生成的令牌。
|
||||||
|
|
||||||
|
注意,具体的 API Base 的格式取决于你所使用的客户端。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A(用户)
|
||||||
|
A --->|请求| B(One API)
|
||||||
|
B -->|中继请求| C(OpenAI)
|
||||||
|
B -->|中继请求| D(Azure)
|
||||||
|
B -->|中继请求| E(其他下游渠道)
|
||||||
|
```
|
||||||
|
|
||||||
可以通过在令牌后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。
|
可以通过在令牌后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。
|
||||||
注意,需要是管理员用户创建的令牌才能指定渠道 ID。
|
注意,需要是管理员用户创建的令牌才能指定渠道 ID。
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ const (
|
|||||||
type GeneralOpenAIRequest struct {
|
type GeneralOpenAIRequest struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Messages []Message `json:"messages"`
|
Messages []Message `json:"messages"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt any `json:"prompt"`
|
||||||
Stream bool `json:"stream"`
|
Stream bool `json:"stream"`
|
||||||
//MaxTokens int `json:"max_tokens"`
|
//MaxTokens int `json:"max_tokens"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature float64 `json:"temperature"`
|
||||||
@ -188,7 +188,7 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
case RelayModeChatCompletions:
|
case RelayModeChatCompletions:
|
||||||
promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model)
|
promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model)
|
||||||
case RelayModeCompletions:
|
case RelayModeCompletions:
|
||||||
promptTokens = countTokenText(textRequest.Prompt, textRequest.Model)
|
promptTokens = countTokenInput(textRequest.Prompt, textRequest.Model)
|
||||||
case RelayModeModeration:
|
case RelayModeModeration:
|
||||||
promptTokens = countTokenInput(textRequest.Input, textRequest.Model)
|
promptTokens = countTokenInput(textRequest.Input, textRequest.Model)
|
||||||
}
|
}
|
||||||
@ -260,7 +260,10 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
common.SysError("Error consuming token remain quota: " + err.Error())
|
common.SysError("Error consuming token remain quota: " + err.Error())
|
||||||
}
|
}
|
||||||
userId := c.GetInt("id")
|
userId := c.GetInt("id")
|
||||||
model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f)", textRequest.Model, quota, modelRatio, groupRatio, completionRatio))
|
model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f)", textRequest.Model, quota, modelRatio, groupRatio))
|
||||||
|
model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
|
||||||
|
channelId := c.GetInt("channel_id")
|
||||||
|
model.UpdateChannelUsedQuota(channelId, quota)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ type Channel struct {
|
|||||||
BalanceUpdatedTime int64 `json:"balance_updated_time" gorm:"bigint"`
|
BalanceUpdatedTime int64 `json:"balance_updated_time" gorm:"bigint"`
|
||||||
Models string `json:"models"`
|
Models string `json:"models"`
|
||||||
Group string `json:"group" gorm:"type:varchar(32);default:'default'"`
|
Group string `json:"group" gorm:"type:varchar(32);default:'default'"`
|
||||||
|
UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
|
func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
|
||||||
@ -136,3 +138,10 @@ func UpdateChannelStatusById(id int, status int) {
|
|||||||
common.SysError("failed to update channel status: " + err.Error())
|
common.SysError("failed to update channel status: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateChannelUsedQuota(id int, quota int) {
|
||||||
|
err := DB.Model(&Channel{}).Where("id = ?", id).Update("used_quota", gorm.Expr("used_quota + ?", quota)).Error
|
||||||
|
if err != nil {
|
||||||
|
common.SysError("failed to update channel used quota: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,6 +23,8 @@ type User struct {
|
|||||||
VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
|
VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
|
||||||
AccessToken string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management
|
AccessToken string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management
|
||||||
Quota int `json:"quota" gorm:"type:int;default:0"`
|
Quota int `json:"quota" gorm:"type:int;default:0"`
|
||||||
|
UsedQuota int `json:"used_quota" gorm:"type:int;default:0;column:used_quota"` // used quota
|
||||||
|
RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number
|
||||||
Group string `json:"group" gorm:"type:varchar(32);default:'default'"`
|
Group string `json:"group" gorm:"type:varchar(32);default:'default'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,3 +264,15 @@ func GetRootUserEmail() (email string) {
|
|||||||
DB.Model(&User{}).Where("role = ?", common.RoleRootUser).Select("email").Find(&email)
|
DB.Model(&User{}).Where("role = ?", common.RoleRootUser).Select("email").Find(&email)
|
||||||
return email
|
return email
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateUserUsedQuotaAndRequestCount(id int, quota int) {
|
||||||
|
err := DB.Model(&User{}).Where("id = ?", id).Updates(
|
||||||
|
map[string]interface{}{
|
||||||
|
"used_quota": gorm.Expr("used_quota + ?", quota),
|
||||||
|
"request_count": gorm.Expr("request_count + ?", 1),
|
||||||
|
},
|
||||||
|
).Error
|
||||||
|
if err != nil {
|
||||||
|
common.SysError("Failed to update user used quota and request count: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { API, showError, showSuccess } from '../helpers';
|
import { API, showError, showSuccess } from '../helpers';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE } from '../constants';
|
import { ITEMS_PER_PAGE } from '../constants';
|
||||||
import { renderGroup, renderText } from '../helpers/render';
|
import { renderGroup, renderNumber, renderText } from '../helpers/render';
|
||||||
|
|
||||||
function renderRole(role) {
|
function renderRole(role) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
@ -197,7 +197,7 @@ const UsersTable = () => {
|
|||||||
sortUser('quota');
|
sortUser('quota');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
剩余额度
|
统计信息
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
@ -241,7 +241,11 @@ const UsersTable = () => {
|
|||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>{renderGroup(user.group)}</Table.Cell>
|
<Table.Cell>{renderGroup(user.group)}</Table.Cell>
|
||||||
<Table.Cell>{user.email ? renderText(user.email, 30) : '无'}</Table.Cell>
|
<Table.Cell>{user.email ? renderText(user.email, 30) : '无'}</Table.Cell>
|
||||||
<Table.Cell>{user.quota}</Table.Cell>
|
<Table.Cell>
|
||||||
|
<Popup content='剩余额度' trigger={<Label>{renderNumber(user.quota)}</Label>} />
|
||||||
|
<Popup content='已用额度' trigger={<Label>{renderNumber(user.used_quota)}</Label>} />
|
||||||
|
<Popup content='请求次数' trigger={<Label>{renderNumber(user.request_count)}</Label>} />
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>{renderRole(user.role)}</Table.Cell>
|
<Table.Cell>{renderRole(user.role)}</Table.Cell>
|
||||||
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
|
@ -8,19 +8,31 @@ export function renderText(text, limit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function renderGroup(group) {
|
export function renderGroup(group) {
|
||||||
if (group === "") {
|
if (group === '') {
|
||||||
return <Label>default</Label>
|
return <Label>default</Label>;
|
||||||
}
|
}
|
||||||
let groups = group.split(",");
|
let groups = group.split(',');
|
||||||
groups.sort();
|
groups.sort();
|
||||||
return <>
|
return <>
|
||||||
{groups.map((group) => {
|
{groups.map((group) => {
|
||||||
if (group === "vip" || group === "pro") {
|
if (group === 'vip' || group === 'pro') {
|
||||||
return <Label color='yellow'>{group}</Label>
|
return <Label color='yellow'>{group}</Label>;
|
||||||
} else if (group === "svip" || group === "premium") {
|
} else if (group === 'svip' || group === 'premium') {
|
||||||
return <Label color='red'>{group}</Label>
|
return <Label color='red'>{group}</Label>;
|
||||||
}
|
}
|
||||||
return <Label>{group}</Label>
|
return <Label>{group}</Label>;
|
||||||
})}
|
})}
|
||||||
</>
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderNumber(num) {
|
||||||
|
if (num >= 1000000000) {
|
||||||
|
return (num / 1000000000).toFixed(1) + 'B';
|
||||||
|
} else if (num >= 1000000) {
|
||||||
|
return (num / 1000000).toFixed(1) + 'M';
|
||||||
|
} else if (num >= 10000) {
|
||||||
|
return (num / 1000).toFixed(1) + 'k';
|
||||||
|
} else {
|
||||||
|
return num;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user