refactor: 完成管理后台的系统设置页面重构

This commit is contained in:
RockYang 2023-06-19 15:58:52 +08:00
parent fe7f021ddb
commit fda811de97
9 changed files with 204 additions and 245 deletions

View File

@ -137,8 +137,7 @@ func authorizeMiddleware(s *AppServer) gin.HandlerFunc {
if c.Request.URL.Path == "/api/user/login" ||
c.Request.URL.Path == "/api/admin/login" ||
c.Request.URL.Path == "/api/user/register" ||
c.Request.URL.Path == "/api/apikey/add" ||
c.Request.URL.Path == "/api/apikey/list" {
c.Request.URL.Path == "/api/config/get" {
c.Next()
return
}

View File

@ -49,6 +49,4 @@ type SystemConfig struct {
UserInitCalls int `json:"user_init_calls"` // 新用户注册默认总送多少次调用
}
var GptModels = []string{"gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-16k-0613", "gpt-4", "gpt-4-0613", "gpt-4-32k", "gpt-4-32k-0613"}
const UserInitCalls = 1000

View File

@ -1,8 +1,9 @@
package handler
package admin
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/handler"
"chatplus/store/model"
"chatplus/utils"
"chatplus/utils/resp"
@ -12,14 +13,14 @@ import (
)
type ConfigHandler struct {
BaseHandler
handler.BaseHandler
db *gorm.DB
}
func NewConfigHandler(app *core.AppServer, db *gorm.DB) *ConfigHandler {
handler := ConfigHandler{db: db}
handler.App = app
return &handler
h := ConfigHandler{db: db}
h.App = app
return &h
}
func (h *ConfigHandler) Update(c *gin.Context) {
@ -71,8 +72,3 @@ func (h *ConfigHandler) Get(c *gin.Context) {
resp.SUCCESS(c, m)
}
// AllGptModels 获取所有的 GPT 模型
func (h *ConfigHandler) AllGptModels(c *gin.Context) {
resp.SUCCESS(c, types.GptModels)
}

View File

@ -85,7 +85,7 @@ func main() {
fx.Provide(handler.NewChatRoleHandler),
fx.Provide(handler.NewUserHandler),
fx.Provide(handler.NewChatHandler),
fx.Provide(handler.NewConfigHandler),
fx.Provide(admin.NewConfigHandler),
fx.Provide(admin.NewAdminHandler),
fx.Provide(admin.NewApiKeyHandler),
@ -117,13 +117,13 @@ func main() {
group.GET("tokens", h.Tokens)
group.GET("stop", h.StopGenerate)
}),
fx.Invoke(func(s *core.AppServer, h *handler.ConfigHandler) {
group := s.Engine.Group("/api/config/")
//
fx.Invoke(func(s *core.AppServer, h *admin.ConfigHandler) {
group := s.Engine.Group("/api/admin/config/")
group.POST("update", h.Update)
group.GET("get", h.Get)
group.GET("models", h.AllGptModels)
}),
fx.Invoke(func(s *core.AppServer, h *admin.ManagerHandler) {
group := s.Engine.Group("/api/admin/")
group.POST("login", h.Login)

View File

@ -200,7 +200,8 @@ import {
Close,
Delete,
Edit,
Plus, Promotion,
Plus,
Promotion,
RefreshRight,
Search,
Tools,
@ -270,7 +271,7 @@ if (!user.value) {
});
//
httpGet('/api/config/get?key=system').then(res => {
httpGet('/api/admin/config/get?key=system').then(res => {
title.value = res.data.title;
models.value = res.data.models;
}).catch(e => {
@ -731,8 +732,10 @@ $borderColor = #4676d0;
height: 100%;
// left side
.el-aside {
background-color: $sideBgColor;
.title-box {
padding: 6px 10px;
display: flex;

View File

@ -94,16 +94,15 @@
</template>
<script setup>
import {computed, defineComponent, onMounted, ref} from 'vue'
import {computed, onMounted, ref} from 'vue'
import {Fold, Menu} from "@element-plus/icons-vue"
import XWelcome from "@/views/admin/Welcome.vue";
import SysConfig from "@/views/admin/SysConfig.vue";
import {arrayContains, removeArrayItem} from "@/utils/libs";
import UserList from "@/views/admin/UserList.vue";
import RoleList from "@/views/admin/RoleList.vue";
import {httpGet, httpPost} from "@/utils/http";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import {setLoginUser} from "@/utils/storage";
import {useRouter} from "vue-router";
const title = ref('Chat-Plus 控制台')
@ -118,7 +117,7 @@ const navs = ref([
},
{
id: 2,
title: '口令管理',
title: '用户管理',
tab: 'user',
active: false,
},
@ -151,9 +150,17 @@ onMounted(() => {
winHeight.value = window.innerHeight
})
//
httpGet("/api/admin/session").catch(() => {
router.push('/admin/login')
})
//
httpGet('/api/admin/config/get?key=system').then(res => {
title.value = res.data['admin_title'];
}).catch(e => {
ElMessage.error("加载系统配置失败: " + e.message)
})
})
const logout = function () {
@ -286,6 +293,7 @@ $borderColor = #4676d0;
.nav-footer {
flex-direction column
div {
padding 10px 20px;
font-size 14px;
@ -311,7 +319,6 @@ $borderColor = #4676d0;
.el-main {
--el-main-padding: 0;
margin: 0;
background-image url("~@/assets/img/bg_01.jpeg")
.main-container {
display: flex;
@ -325,6 +332,10 @@ $borderColor = #4676d0;
height 35px
line-height 35px
}
.el-tabs__content {
padding 10px 20px 20px 20px;
}
}
}
@ -332,5 +343,4 @@ $borderColor = #4676d0;
}
</style>

View File

@ -1,213 +1,183 @@
<template>
<div class="system-config" v-loading="loading">
<el-form :model="form" label-width="120px">
<el-form-item label="应用标题">
<el-input v-model="form['title']"/>
<div class="container">
<el-divider content-position="center">基本设置</el-divider>
<el-form :model="system" label-width="120px" label-position="left" ref="systemFormRef" :rules="rules">
<el-form-item label="网站标题" prop="title">
<el-input v-model="system['title']"/>
</el-form-item>
<el-form-item label="控制台标题">
<el-input v-model="form['console_title']"/>
<el-form-item label="控制台标题" prop="admin_title">
<el-input v-model="system['admin_title']"/>
</el-form-item>
<el-form-item label="代理地址">
<el-input v-model="form['proxy_url']" placeholder="多个地址之间用逗号隔开"/>
</el-form-item>
<el-form-item label="微信群聊二维码">
<el-input v-model="form['img_url']['wechat_group']" placeholder="群聊二维码地址"/>
</el-form-item>
<el-form-item label="个人微信名片">
<el-input v-model="form['img_url']['wechat_card']" placeholder="名片二维码地址"/>
</el-form-item>
<el-divider content-position="center">聊天设置</el-divider>
<el-row>
<el-col :span="12">
<div class="grid-content">
<el-form-item label="GPT模型">
<el-input v-model="form['model']" placeholder="gpt-3/gpt-3.5-turbo/gpt-4"/>
<el-form-item label="注册赠送次数" prop="init_calls">
<el-input v-model.number="system['init_calls']" placeholder="新用户注册赠送对话次数"/>
</el-form-item>
<el-alert type="info" show-icon :closable="false">
<p>在这里维护前端聊天页面可用的 GPT 模型列表</p>
</el-alert>
<el-form-item label="GPT 模型" prop="models">
<div class="models">
<el-tag
v-for="item in system.models"
:key="item"
@close="removeModel(item)"
round
closable
>
{{ item }}
</el-tag>
<el-button type="success" :icon="Plus" @click="addModel" size="small" circle/>
</div>
</el-col>
<el-col :span="12">
<div class="grid-content">
<el-form-item label="模型温度">
<el-input v-model="form['temperature']" placeholder="0-1之间的小数"/>
</el-form-item>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<div class="grid-content">
<el-form-item label="Max Tokens">
<el-input v-model="form['max_tokens']" placeholder="回复的最大字数最大4096"/>
</el-form-item>
</div>
</el-col>
<el-col :span="12">
<div class="grid-content">
<el-form-item label="上下文超时">
<el-input v-model="form['chat_context_expire_time']" placeholder="单位:秒"/>
</el-form-item>
</div>
</el-col>
</el-row>
<el-form-item label="对话上下文">
<el-switch v-model="form['enable_context']"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveConfig">保存</el-button>
<el-button type="primary" @click="save('system')">保存</el-button>
</el-form-item>
</el-form>
<el-divider content-position="center">API KEY 管理</el-divider>
<el-row class="api-key-box">
<el-input
v-model="apiKey"
placeholder="输入 API KEY"
class="input-with-select"
>
<template #prepend>
<el-button type="primary">
<el-icon>
<Plus/>
</el-icon>
</el-button>
</template>
<template #append>
<el-button class="new-proxy" @click="addApiKey">新增</el-button>
</template>
</el-input>
</el-row>
<el-row>
<el-table :data="apiKeys" style="width: 100%">
<el-table-column prop="value" label="API-KEY"/>
<el-table-column prop="last_used" label="最后使用" width="180">
<template #default="scope">
<span v-if="scope.row['last_used'] > 0">{{ dateFormat(scope.row['last_used']) }}</span>
<el-tag v-else>未使用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="scope">
<el-popconfirm
width="220"
confirm-button-text="确定"
cancel-button-text="取消"
title="确定删除该记录吗?"
:hide-after="0"
@confirm="removeApiKey(scope.row.value)"
>
<template #reference>
<el-button
size="small"
type="danger">删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-row>
<el-divider content-position="center">聊天设置</el-divider>
<el-alert type="info" show-icon :closable="false">
<p>以下配置为新用户注册默认初始化的聊天参数用户登录后还可以自己修改参数</p>
</el-alert>
<el-form :model="chat" label-position="left" label-width="120px">
<el-form-item label="OpenAI API 地址">
<el-input v-model="chat['api_url']" placeholder="gpt-3/gpt-3.5-turbo/gpt-4"/>
</el-form-item>
<el-form-item label="默认模型">
<el-input v-model="chat['model']" placeholder="用户默认使用的 GPT 模型"/>
</el-form-item>
<el-form-item label="模型温度">
<el-input v-model="chat['temperature']" placeholder="0-1之间的小数"/>
</el-form-item>
<el-form-item label="Max Tokens">
<el-input v-model="chat['max_tokens']" placeholder="回复的最大字数最大4096"/>
</el-form-item>
<el-form-item label="开启聊天上下文">
<el-switch v-model="chat['enable_context']"/>
</el-form-item>
<el-form-item label="保存聊天记录">
<el-switch v-model="chat['enable_history']"/>
</el-form-item>
<el-form-item style="text-align: right">
<el-button type="primary" @click="save('chat')">保存</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import {defineComponent, nextTick} from "vue";
import {Plus} from "@element-plus/icons-vue";
<script setup>
import {nextTick, onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat, removeArrayItem} from "@/utils/libs";
import {ElMessage, ElMessageBox} from "element-plus";
import {Plus} from "@element-plus/icons-vue";
import {removeArrayItem} from "@/utils/libs";
export default defineComponent({
name: 'SysConfig',
components: {Plus},
data() {
return {
apiKey: '',
form: {img_url: {}},
apiKeys: [],
loading: true
}
},
mounted() {
//
httpGet('/api/admin/config/get').then((res) => {
this.form = res.data;
}).catch(() => {
ElMessage.error('获取系统配置失败')
const system = ref({models: []})
const chat = ref({})
const loading = ref(true)
const systemFormRef = ref(null)
const tempModel = ref('')
const models = ref([])
onMounted(() => {
//
httpGet('/api/admin/config/get?key=system').then(res => {
system.value = res.data
system.value['models'].forEach(model => {
models.value.push({
name: model,
edit: false
})
})
// API KEYS
httpPost('api/admin/apikey/list').then((res) => {
this.apiKeys = res.data
}).catch(() => {
ElMessage.error('获取 API KEY 失败')
}).catch(e => {
ElMessage.error("加载系统配置失败: " + e.message)
})
//
httpGet('/api/admin/config/get?key=chat').then(res => {
chat.value = res.data
}).catch(e => {
ElMessage.error("加载聊天配置失败: " + e.message)
})
nextTick(() => {
this.loading = false
loading.value = false
})
},
computed: {
dateFormat() {
return dateFormat
},
},
methods: {
saveConfig: function () {
this.form['temperature'] = parseFloat(this.form.temperature)
this.form['chat_context_expire_time'] = parseInt(this.form.chat_context_expire_time)
this.form['max_tokens'] = parseInt(this.form.max_tokens)
httpPost("/api/admin/config/set", this.form).then(() => {
ElMessage.success("保存成功");
}).catch((e) => {
console.log(e.message);
ElMessage.error("保存失败");
})
},
addApiKey: function () {
if (this.apiKey.trim() === '') {
ElMessage.error('请输入 API KEY')
return
}
httpPost('api/admin/apikey/add', {api_key: this.apiKey.trim()}).then(() => {
ElMessage.success('添加成功')
this.apiKeys.unshift({value: this.apiKey, last_used: 0})
this.apiKey = ''
}).catch((e) => {
ElMessage.error('添加失败,' + e.message)
const rules = reactive({
title: [{required: true, message: '请输入网站标题', trigger: 'blur',}],
admin_title: [{required: true, message: '请输入控制台标题', trigger: 'blur',}],
init_calls: [{required: true, message: '必须填入大于0的数组', trigger: 'blur',}],
models: [{required: true, message: '至少保留一个 GPT 模型', trigger: 'blur',}],
})
},
removeApiKey: function (key) {
httpPost('api/admin/apikey/remove', {api_key: key}).then(() => {
ElMessage.success('删除成功')
this.apiKeys = removeArrayItem(this.apiKeys, key, function (v1, v2) {
return v1.value === v2
})
}).catch((e) => {
ElMessage.error('删除失败,' + e.message)
const save = function (key) {
systemFormRef.value.validate((valid) => {
if (valid) {
const data = key === 'system' ? system.value : chat.value
httpPost('/api/admin/config/update', {key: key, config: data}).then(() => {
ElMessage.success("操作成功!")
}).catch(e => {
ElMessage.error("操作失败:" + e.message)
})
}
}
})
}
const removeModel = function (model) {
system.value.models = removeArrayItem(system.value.models, model, (v1, v2) => {
return v1 === v2
})
}
// GPT
const addModel = function () {
ElMessageBox.prompt('请输入 GPT 模型名称', '新增模型', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern:
/[\w+]/,
inputErrorMessage: '请输入模型名称',
}).then(({value}) => {
system.value.models.push(value)
})
}
</script>
<style lang="stylus" scoped>
.system-config {
display flex
justify-content center
.api-key-box {
padding-bottom: 10px;
.container {
width 100%
max-width 800px;
.el-form {
.el-form-item__content {
.models {
.el-tag {
margin-right 10px;
.el-input {
max-width 100px;
}
}
.el-button--small {
font-size 16px;
}
}
}
}
.el-alert {
margin-bottom 15px;
}
}
}

View File

@ -1,21 +1,5 @@
<template>
<div class="user-list" v-loading="loading">
<el-row class="opt-box">
<el-button type="primary" @click="showUserDialog = true">
<el-icon>
<Plus/>
</el-icon>
新增口令
</el-button>
<el-button type="success" @click="showBatchAddUserDialog = true">
<el-icon>
<Plus/>
</el-icon>
批量生成口令
</el-button>
</el-row>
<el-row>
<el-table :data="users">
<el-table-column prop="name" label="口令名称"/>
@ -258,7 +242,6 @@
<script setup>
import {nextTick, onMounted, reactive, ref} from "vue";
import {Plus} from "@element-plus/icons-vue";
import {httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {arrayContains, removeArrayItem} from "@/utils/libs";

View File

@ -1,6 +1,6 @@
<template>
<div class="welcome" :style="{ height: winHeight + 'px' }">
<h1>Chat-Plus 控制台</h1>
<h1>ChatGPT-PLUS 控制台</h1>
</div>
</template>