Merge pull request #7 from songquanpeng/main
Fork Sync: Update from parent repository
This commit is contained in:
commit
8816250cc9
15
README.md
15
README.md
@ -44,8 +44,7 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
|||||||
<a href="https://iamazing.cn/page/reward">赞赏支持</a>
|
<a href="https://iamazing.cn/page/reward">赞赏支持</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> **Warning**:从 `v0.2` 版本升级到 `v0.3` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.2-v0.3.sql)。
|
> **Warning**:使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。
|
||||||
|
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
1. 支持多种 API 访问渠道,欢迎 PR 或提 issue 添加更多渠道:
|
1. 支持多种 API 访问渠道,欢迎 PR 或提 issue 添加更多渠道:
|
||||||
@ -65,16 +64,18 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
|||||||
5. 支持**令牌管理**,设置令牌的过期时间和使用次数。
|
5. 支持**令牌管理**,设置令牌的过期时间和使用次数。
|
||||||
6. 支持**兑换码管理**,支持批量生成和导出兑换码,可使用兑换码为账户进行充值。
|
6. 支持**兑换码管理**,支持批量生成和导出兑换码,可使用兑换码为账户进行充值。
|
||||||
7. 支持**通道管理**,批量创建通道。
|
7. 支持**通道管理**,批量创建通道。
|
||||||
8. 支持发布公告,设置充值链接,设置新用户初始额度。
|
8. 支持**用户分组**以及**渠道分组**。
|
||||||
9. 支持丰富的**自定义**设置,
|
9. 支持渠道**设置模型列表**。
|
||||||
|
10. 支持发布公告,设置充值链接,设置新用户初始额度。
|
||||||
|
11. 支持丰富的**自定义**设置,
|
||||||
1. 支持自定义系统名称,logo 以及页脚。
|
1. 支持自定义系统名称,logo 以及页脚。
|
||||||
2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
|
2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
|
||||||
10. 支持通过系统访问令牌访问管理 API。
|
12. 支持通过系统访问令牌访问管理 API。
|
||||||
11. 支持用户管理,支持**多种用户登录注册方式**:
|
13. 支持用户管理,支持**多种用户登录注册方式**:
|
||||||
+ 邮箱登录注册以及通过邮箱进行密码重置。
|
+ 邮箱登录注册以及通过邮箱进行密码重置。
|
||||||
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
|
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
|
||||||
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
|
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
|
||||||
12. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
|
14. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
### 基于 Docker 进行部署
|
### 基于 Docker 进行部署
|
||||||
|
@ -228,7 +228,7 @@ func GetUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
myRole := c.GetInt("role")
|
myRole := c.GetInt("role")
|
||||||
if myRole <= user.Role {
|
if myRole <= user.Role && myRole != common.RoleRootUser {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权获取同级或更高等级用户的信息",
|
"message": "无权获取同级或更高等级用户的信息",
|
||||||
@ -326,14 +326,14 @@ func UpdateUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
myRole := c.GetInt("role")
|
myRole := c.GetInt("role")
|
||||||
if myRole <= originUser.Role {
|
if myRole <= originUser.Role && myRole != common.RoleRootUser {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权更新同权限等级或更高权限等级的用户信息",
|
"message": "无权更新同权限等级或更高权限等级的用户信息",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if myRole <= updatedUser.Role {
|
if myRole <= updatedUser.Role && myRole != common.RoleRootUser {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权将其他用户权限等级提升到大于等于自己的权限等级",
|
"message": "无权将其他用户权限等级提升到大于等于自己的权限等级",
|
||||||
|
@ -9,7 +9,7 @@ type Ability struct {
|
|||||||
Group string `json:"group" gorm:"type:varchar(32);primaryKey;autoIncrement:false"`
|
Group string `json:"group" gorm:"type:varchar(32);primaryKey;autoIncrement:false"`
|
||||||
Model string `json:"model" gorm:"primaryKey;autoIncrement:false"`
|
Model string `json:"model" gorm:"primaryKey;autoIncrement:false"`
|
||||||
ChannelId int `json:"channel_id" gorm:"primaryKey;autoIncrement:false;index"`
|
ChannelId int `json:"channel_id" gorm:"primaryKey;autoIncrement:false;index"`
|
||||||
Enabled bool `json:"enabled" gorm:"default:1"`
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) {
|
func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) {
|
||||||
@ -68,5 +68,5 @@ func (channel *Channel) UpdateAbilities() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateAbilityStatus(channelId int, status bool) error {
|
func UpdateAbilityStatus(channelId int, status bool) error {
|
||||||
return DB.Model(&Ability{}).Where("channel_id = ?", channelId).Update("enabled", status).Error
|
return DB.Model(&Ability{}).Where("channel_id = ?", channelId).Select("enabled").Update("enabled", status).Error
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ func (channel *Channel) Update() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
DB.Model(channel).First(channel, "id = ?", channel.Id)
|
||||||
err = channel.UpdateAbilities()
|
err = channel.UpdateAbilities()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -302,7 +302,6 @@ const UsersTable = () => {
|
|||||||
size={'small'}
|
size={'small'}
|
||||||
as={Link}
|
as={Link}
|
||||||
to={'/user/edit/' + user.id}
|
to={'/user/edit/' + user.id}
|
||||||
disabled={user.role === 100}
|
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Header, Message, Segment } from 'semantic-ui-react';
|
import { Button, Form, Header, Message, Segment } from 'semantic-ui-react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { API, showError, showSuccess } from '../../helpers';
|
import { API, showError, showInfo, showSuccess } from '../../helpers';
|
||||||
import { CHANNEL_OPTIONS } from '../../constants';
|
import { CHANNEL_OPTIONS } from '../../constants';
|
||||||
|
|
||||||
const EditChannel = () => {
|
const EditChannel = () => {
|
||||||
@ -15,13 +15,15 @@ const EditChannel = () => {
|
|||||||
key: '',
|
key: '',
|
||||||
base_url: '',
|
base_url: '',
|
||||||
other: '',
|
other: '',
|
||||||
|
group: 'default',
|
||||||
models: [],
|
models: [],
|
||||||
};
|
};
|
||||||
const [batch, setBatch] = useState(false);
|
const [batch, setBatch] = useState(false);
|
||||||
const [inputs, setInputs] = useState(originInputs);
|
const [inputs, setInputs] = useState(originInputs);
|
||||||
const [modelOptions, setModelOptions] = useState([]);
|
const [modelOptions, setModelOptions] = useState([]);
|
||||||
|
const [basicModels, setBasicModels] = useState([]);
|
||||||
|
const [fullModels, setFullModels] = useState([]);
|
||||||
const handleInputChange = (e, { name, value }) => {
|
const handleInputChange = (e, { name, value }) => {
|
||||||
console.log(name, value);
|
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,8 +51,10 @@ const EditChannel = () => {
|
|||||||
text: model.id,
|
text: model.id,
|
||||||
value: model.id,
|
value: model.id,
|
||||||
})));
|
})));
|
||||||
|
setFullModels(res.data.data.map((model) => model.id));
|
||||||
|
setBasicModels(res.data.data.filter((model) => !model.id.startsWith("gpt-4")).map((model) => model.id));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching models:', error);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,7 +66,10 @@ const EditChannel = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!isEdit && (inputs.name === '' || inputs.key === '')) return;
|
if (!isEdit && (inputs.name === '' || inputs.key === '')) {
|
||||||
|
showInfo('请填写渠道名称和渠道密钥!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
let localInputs = inputs;
|
let localInputs = inputs;
|
||||||
if (localInputs.base_url.endsWith('/')) {
|
if (localInputs.base_url.endsWith('/')) {
|
||||||
localInputs.base_url = localInputs.base_url.slice(0, localInputs.base_url.length - 1);
|
localInputs.base_url = localInputs.base_url.slice(0, localInputs.base_url.length - 1);
|
||||||
@ -159,9 +166,20 @@ const EditChannel = () => {
|
|||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<Form.Input
|
||||||
|
label='分组'
|
||||||
|
name='group'
|
||||||
|
placeholder={'请输入分组'}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={inputs.group}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Dropdown
|
<Form.Dropdown
|
||||||
label='支持的模型'
|
label='模型'
|
||||||
|
placeholder={'请选择该通道所支持的模型'}
|
||||||
name='models'
|
name='models'
|
||||||
fluid
|
fluid
|
||||||
multiple
|
multiple
|
||||||
@ -172,6 +190,14 @@ const EditChannel = () => {
|
|||||||
options={modelOptions}
|
options={modelOptions}
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
<div style={{ lineHeight: '40px', marginBottom: '12px'}}>
|
||||||
|
<Button type={'button'} onClick={() => {
|
||||||
|
handleInputChange(null, { name: 'models', value: basicModels });
|
||||||
|
}}>填入基础模型</Button>
|
||||||
|
<Button type={'button'} onClick={() => {
|
||||||
|
handleInputChange(null, { name: 'models', value: fullModels });
|
||||||
|
}}>填入所有模型</Button>
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
batch ? <Form.Field>
|
batch ? <Form.Field>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
|
@ -15,8 +15,9 @@ const EditUser = () => {
|
|||||||
wechat_id: '',
|
wechat_id: '',
|
||||||
email: '',
|
email: '',
|
||||||
quota: 0,
|
quota: 0,
|
||||||
|
group: 'default'
|
||||||
});
|
});
|
||||||
const { username, display_name, password, github_id, wechat_id, email, quota } =
|
const { username, display_name, password, github_id, wechat_id, email, quota, group } =
|
||||||
inputs;
|
inputs;
|
||||||
const handleInputChange = (e, { name, value }) => {
|
const handleInputChange = (e, { name, value }) => {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
@ -98,7 +99,17 @@ const EditUser = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
{
|
{
|
||||||
userId && (
|
userId && <>
|
||||||
|
<Form.Field>
|
||||||
|
<Form.Input
|
||||||
|
label='分组'
|
||||||
|
name='group'
|
||||||
|
placeholder={'请输入用户分组'}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={group}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='剩余额度'
|
label='剩余额度'
|
||||||
@ -110,7 +121,7 @@ const EditUser = () => {
|
|||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
)
|
</>
|
||||||
}
|
}
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
|
Loading…
Reference in New Issue
Block a user