feat: 支持文心4.0模型,不同的用户可以订阅不同的AI模型

This commit is contained in:
RockYang 2023-10-26 13:41:49 +08:00
parent 5b5150e6d4
commit 01205af018
30 changed files with 193 additions and 47 deletions

View File

@ -4,11 +4,12 @@
ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。主要有如下特性 ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。主要有如下特性
* 完整的开源系统,前端应用和后台管理系统皆可开箱即用。 * 完整的开源系统,前端应用和后台管理系统皆可开箱即用。
* 聊天体验跟 ChatGPT 官方版本完全一致。 * 基于 Websocket 实现,完美的打字机体验。
* 内置了各种预训练好的角色,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。 * 内置了各种预训练好的角色应用,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。
* 支持 OPenAIAzure文心一言讯飞星火清华 ChatGLM等多个大语言模型。
* 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。 * 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。
* 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。(可定制开发其他支付通道支持) * 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。(可定制开发其他支付通道支持)
* 集成插件 API 功能,可结合 GPT 开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。 * 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。
## 功能截图 ## 功能截图
@ -16,34 +17,30 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
![ChatGPT Chat Page](/docs/imgs/gpt.gif) ![ChatGPT Chat Page](/docs/imgs/gpt.gif)
### 新版聊天界面 ### AI 对话界面
![ChatGPT new Chat Page](/docs/imgs/chat-new.png) ![ChatGPT new Chat Page](/docs/imgs/chat-new.png)
### MidJourney 专业绘画界面(v3.1.3) ### MidJourney 专业绘画界面
![mid-journey](/docs/imgs/mj_image.jpg)
![ChatGPT-midjourney](/docs/imgs/mj_image.png) ### Stable-Diffusion 专业绘画页面
![Stable-Diffusion](/docs/imgs/sd_image.jpg)
![Stable-Diffusion](/docs/imgs/sd_image_detail.jpg)
### 绘图作品展
![ChatGPT image_list](/docs/imgs/image-list.png)
### AI应用列表
![ChatGPT-app-list](/docs/imgs/app-list.jpg)
### 自动调用函数插件 ### 自动调用函数插件
![ChatGPT function plugin](/docs/imgs/plugin.png) ![ChatGPT function plugin](/docs/imgs/plugin.png)
![ChatGPT function plugin](/docs/imgs/mj.jpg) ![ChatGPT function plugin](/docs/imgs/mj.jpg)
### 绘图作品展
![ChatGPT image_list](/docs/imgs/image-list.png)
### 登录页面
![ChatGPT Login](/docs/imgs/login.png)
### 管理后台 ### 管理后台
![ChatGPT admin](/docs/imgs/admin_dashboard.png) ![ChatGPT admin](/docs/imgs/admin_dashboard.png)
![ChatGPT admin](/docs/imgs/admin_config.jpg)
![ChatGPT admin](/docs/imgs/admin_config.png) ![ChatGPT admin](/docs/imgs/admin_models.jpg)
![ChatGPT admin](/docs/imgs/admin_user.png) ![ChatGPT admin](/docs/imgs/admin_user.png)
### 移动端 Web 页面 ### 移动端 Web 页面
@ -306,8 +303,8 @@ docker-compose up -d
![add API Key](docs/imgs/apikey_add.png) ![add API Key](docs/imgs/apikey_add.png)
最后登录前端聊天页面 [http://localhost:8080/chat](http://localhost:8080/chat) 最后进入前端聊天页面 [http://localhost:8080/chat](http://localhost:8080/chat)
你可以注册新用户,也可以使用系统默认有个账号:`geekmaster/12345678` 登录聊天。 你可以注册新用户,也可以使用系统默认有个账号:`18575670125/12345678` 登录聊天。
祝你使用愉快!!! 祝你使用愉快!!!
@ -411,5 +408,7 @@ make clean linux
![支付宝打赏](docs/imgs/alipay.png) ![支付宝打赏](docs/imgs/alipay.png)
![微信打赏](docs/imgs/wechat-pay.png) ![微信打赏](docs/imgs/wechat-pay.png)
![Star History Chart](https://api.star-history.com/svg?repos=yangjian102621/chatgpt-plus&type=Date)

View File

@ -120,4 +120,5 @@ type SystemConfig struct {
RewardImg string `json:"reward_img"` // 众筹收款二维码地址 RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledFunction bool `json:"enabled_function"` // 启用 API 函数功能 EnabledFunction bool `json:"enabled_function"` // 启用 API 函数功能
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能 EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
} }

View File

@ -32,6 +32,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
SortNum int `json:"sort_num"` SortNum int `json:"sort_num"`
Platform string `json:"platform"` Platform string `json:"platform"`
Weight int `json:"weight"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
} }
if err := c.ShouldBindJSON(&data); err != nil { if err := c.ShouldBindJSON(&data); err != nil {
@ -39,7 +40,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
return return
} }
item := model.ChatModel{Platform: data.Platform, Name: data.Name, Value: data.Value, Enabled: data.Enabled} item := model.ChatModel{Platform: data.Platform, Name: data.Name, Value: data.Value, Enabled: data.Enabled, SortNum: data.SortNum, Weight: data.Weight}
item.Id = data.Id item.Id = data.Id
if item.Id > 0 { if item.Id > 0 {
item.CreatedAt = time.Unix(data.CreatedAt, 0) item.CreatedAt = time.Unix(data.CreatedAt, 0)

View File

@ -67,6 +67,7 @@ func (h *UserHandler) Save(c *gin.Context) {
Calls int `json:"calls"` Calls int `json:"calls"`
ImgCalls int `json:"img_calls"` ImgCalls int `json:"img_calls"`
ChatRoles []string `json:"chat_roles"` ChatRoles []string `json:"chat_roles"`
ChatModels []string `json:"chat_models"`
ExpiredTime string `json:"expired_time"` ExpiredTime string `json:"expired_time"`
Status bool `json:"status"` Status bool `json:"status"`
} }
@ -81,12 +82,13 @@ func (h *UserHandler) Save(c *gin.Context) {
user.Id = data.Id user.Id = data.Id
// 此处需要用 map 更新,用结构体无法更新 0 值 // 此处需要用 map 更新,用结构体无法更新 0 值
res = h.db.Model(&user).Updates(map[string]interface{}{ res = h.db.Model(&user).Updates(map[string]interface{}{
"mobile": data.Mobile, "mobile": data.Mobile,
"calls": data.Calls, "calls": data.Calls,
"img_calls": data.ImgCalls, "img_calls": data.ImgCalls,
"status": data.Status, "status": data.Status,
"chat_roles_json": utils.JsonEncode(data.ChatRoles), "chat_roles_json": utils.JsonEncode(data.ChatRoles),
"expired_time": utils.Str2stamp(data.ExpiredTime), "chat_models_json": utils.JsonEncode(data.ChatModels),
"expired_time": utils.Str2stamp(data.ExpiredTime),
}) })
} else { } else {
salt := utils.RandString(8) salt := utils.RandString(8)
@ -97,6 +99,7 @@ func (h *UserHandler) Save(c *gin.Context) {
Salt: salt, Salt: salt,
Status: true, Status: true,
ChatRoles: utils.JsonEncode(data.ChatRoles), ChatRoles: utils.JsonEncode(data.ChatRoles),
ChatModels: utils.JsonEncode(data.ChatModels),
ExpiredTime: utils.Str2stamp(data.ExpiredTime), ExpiredTime: utils.Str2stamp(data.ExpiredTime),
ChatConfig: utils.JsonEncode(types.UserChatConfig{ ChatConfig: utils.JsonEncode(types.UserChatConfig{
ApiKeys: map[types.Platform]string{ ApiKeys: map[types.Platform]string{

View File

@ -24,8 +24,22 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler {
// List 模型列表 // List 模型列表
func (h *ChatModelHandler) List(c *gin.Context) { func (h *ChatModelHandler) List(c *gin.Context) {
var items []model.ChatModel var items []model.ChatModel
var cms = make([]vo.ChatModel, 0) var chatModels = make([]vo.ChatModel, 0)
res := h.db.Where("enabled = ?", true).Order("sort_num ASC").Find(&items) // 只加载用户订阅的 AI 模型
user, err := utils.GetLoginUser(c, h.db)
if err != nil {
resp.NotAuth(c)
return
}
var models []string
err = utils.JsonDecode(user.ChatModels, &models)
if err != nil {
resp.ERROR(c, "当前用户没有订阅任何模型")
return
}
res := h.db.Where("enabled = ?", true).Where("value IN ?", models).Order("sort_num ASC").Find(&items)
if res.Error == nil { if res.Error == nil {
for _, item := range items { for _, item := range items {
var cm vo.ChatModel var cm vo.ChatModel
@ -34,11 +48,11 @@ func (h *ChatModelHandler) List(c *gin.Context) {
cm.Id = item.Id cm.Id = item.Id
cm.CreatedAt = item.CreatedAt.Unix() cm.CreatedAt = item.CreatedAt.Unix()
cm.UpdatedAt = item.UpdatedAt.Unix() cm.UpdatedAt = item.UpdatedAt.Unix()
cms = append(cms, cm) chatModels = append(chatModels, cm)
} else { } else {
logger.Error(err) logger.Error(err)
} }
} }
} }
resp.SUCCESS(c, cms) resp.SUCCESS(c, chatModels)
} }

View File

@ -393,7 +393,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
req.Messages = nil req.Messages = nil
break break
case types.Baidu: case types.Baidu:
apiURL = h.App.ChatConfig.Baidu.ApiURL apiURL = strings.Replace(h.App.ChatConfig.Baidu.ApiURL, "{model}", req.Model, 1)
break break
default: default:
apiURL = h.App.ChatConfig.OpenAI.ApiURL apiURL = h.App.ChatConfig.OpenAI.ApiURL

View File

@ -82,12 +82,13 @@ func (h *UserHandler) Register(c *gin.Context) {
salt := utils.RandString(8) salt := utils.RandString(8)
user := model.User{ user := model.User{
Password: utils.GenPassword(data.Password, salt), Password: utils.GenPassword(data.Password, salt),
Avatar: "/images/avatar/user.png", Avatar: "/images/avatar/user.png",
Salt: salt, Salt: salt,
Status: true, Status: true,
Mobile: data.Mobile, Mobile: data.Mobile,
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色 ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
ChatConfig: utils.JsonEncode(types.UserChatConfig{ ChatConfig: utils.JsonEncode(types.UserChatConfig{
ApiKeys: map[types.Platform]string{ ApiKeys: map[types.Platform]string{
types.OpenAI: "", types.OpenAI: "",

View File

@ -7,4 +7,5 @@ type ChatModel struct {
Value string // API Key 的值 Value string // API Key 的值
SortNum int SortNum int
Enabled bool Enabled bool
Weight int // 对话权重,每次对话扣减多少次对话额度
} }

View File

@ -11,6 +11,7 @@ type User struct {
ImgCalls int // 剩余绘图次数 ImgCalls int // 剩余绘图次数
ChatConfig string `gorm:"column:chat_config_json"` // 聊天配置 json ChatConfig string `gorm:"column:chat_config_json"` // 聊天配置 json
ChatRoles string `gorm:"column:chat_roles_json"` // 聊天角色 ChatRoles string `gorm:"column:chat_roles_json"` // 聊天角色
ChatModels string `gorm:"column:chat_models_json"` // AI 模型,不同的用户拥有不同的聊天模型
ExpiredTime int64 // 账户到期时间 ExpiredTime int64 // 账户到期时间
Status bool `gorm:"default:true"` // 当前状态 Status bool `gorm:"default:true"` // 当前状态
LastLoginAt int64 // 最后登录时间 LastLoginAt int64 // 最后登录时间

View File

@ -6,4 +6,6 @@ type ChatModel struct {
Name string `json:"name"` Name string `json:"name"`
Value string `json:"value"` Value string `json:"value"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
SortNum int `json:"sort_num"`
Weight int `json:"weight"`
} }

View File

@ -12,6 +12,7 @@ type User struct {
ImgCalls int `json:"img_calls"` ImgCalls int `json:"img_calls"`
ChatConfig types.UserChatConfig `json:"chat_config"` // 聊天配置 ChatConfig types.UserChatConfig `json:"chat_config"` // 聊天配置
ChatRoles []string `json:"chat_roles"` // 聊天角色集合 ChatRoles []string `json:"chat_roles"` // 聊天角色集合
ChatModels []string `json:"chat_models"` // AI模型集合
ExpiredTime int64 `json:"expired_time"` // 账户到期时间 ExpiredTime int64 `json:"expired_time"` // 账户到期时间
Status bool `json:"status"` // 当前状态 Status bool `json:"status"` // 当前状态
LastLoginAt int64 `json:"last_login_at"` // 最后登录时间 LastLoginAt int64 `json:"last_login_at"` // 最后登录时间

View File

@ -0,0 +1,6 @@
-- 增加字段保存用户开通的 AI 模型
ALTER TABLE `chatgpt_users` ADD `chat_models_json` TEXT NOT NULL COMMENT 'AI模型 json' AFTER `chat_roles_json`;
-- 为每个模型设置对话权重
ALTER TABLE `chatgpt_chat_models` ADD `weight` TINYINT(3) NOT NULL COMMENT '对话权重,每次对话扣减多少次对话额度' AFTER `enabled`;
UPDATE `chatgpt_chat_models` SET weight = 1;

BIN
docs/imgs/admin_config.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

BIN
docs/imgs/admin_models.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

BIN
docs/imgs/app-list.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

BIN
docs/imgs/mj_image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

BIN
docs/imgs/sd_image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

View File

@ -0,0 +1,10 @@
.el-form-item__content .tip-input {
display: flex;
width: 100%;
}
.el-form-item__content .tip-input .input {
width: 100%;
}
.el-form-item__content .tip-input .info {
margin-left: 6px;
}

View File

@ -0,0 +1,15 @@
.el-form-item__content {
.tip-input {
display flex
width 100%
.input {
width 100%
}
.info {
margin-left 6px
}
}
}

View File

@ -209,7 +209,7 @@
font-size: 20px; font-size: 20px;
cursor: pointer; cursor: pointer;
} }
.page-mj .inner .task-list-box .finish-job-list .job-item:hover { .page-mj .inner .task-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */ transform: translateY(-10px); /* 向上移动10像素 */
} }

View File

@ -132,7 +132,7 @@
font-size: 20px; font-size: 20px;
cursor: pointer; cursor: pointer;
} }
.page-sd .inner .task-list-box .finish-job-list .job-item:hover { .page-sd .inner .task-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */ transform: translateY(-10px); /* 向上移动10像素 */
} }

View File

@ -80,8 +80,9 @@
} }
} }
} }
}
.animate {
&:hover { &:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */ box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */ transform: translateY(-10px); /* 10 */

View File

@ -332,7 +332,7 @@
<div class="finish-job-list"> <div class="finish-job-list">
<ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16"> <ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16">
<template #default="scope"> <template #default="scope">
<div class="job-item" @click="showTask(scope.item)"> <div class="job-item animate" @click="showTask(scope.item)">
<el-image <el-image
:src="scope.item['img_url']+'?imageView2/1/w/240/h/240/q/75'" :src="scope.item['img_url']+'?imageView2/1/w/240/h/240/q/75'"
fit="cover" fit="cover"

View File

@ -14,6 +14,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="name" label="模型名称"/> <el-table-column prop="name" label="模型名称"/>
<el-table-column prop="value" label="模型值"/> <el-table-column prop="value" label="模型值"/>
<el-table-column prop="weight" label="对话权重"/>
<el-table-column prop="enabled" label="启用状态"> <el-table-column prop="enabled" label="启用状态">
<template #default="scope"> <template #default="scope">
<el-switch v-model="scope.row['enabled']" @change="enable(scope.row)"/> <el-switch v-model="scope.row['enabled']" @change="enable(scope.row)"/>
@ -59,6 +60,27 @@
<el-input v-model="item.value" autocomplete="off"/> <el-input v-model="item.value" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item label="对话权重:" prop="weight">
<template #default>
<div class="tip-input">
<el-input-number :min="1" v-model="item.weight" autocomplete="off"/>
<div class="info">
<el-tooltip
class="box-item"
effect="dark"
content="对话权重,每次对话扣减多少次对话额度"
placement="right"
>
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</div>
</div>
</template>
</el-form-item>
<el-form-item label="启用状态:" prop="enable"> <el-form-item label="启用状态:" prop="enable">
<el-switch v-model="item.enabled"/> <el-switch v-model="item.enabled"/>
</el-form-item> </el-form-item>
@ -79,7 +101,7 @@ import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import {dateFormat, removeArrayItem} from "@/utils/libs"; import {dateFormat, removeArrayItem} from "@/utils/libs";
import {Plus} from "@element-plus/icons-vue"; import {InfoFilled, Plus} from "@element-plus/icons-vue";
import {Sortable} from "sortablejs"; import {Sortable} from "sortablejs";
// //
@ -136,9 +158,11 @@ onMounted(() => {
sortedData.forEach((id, index) => { sortedData.forEach((id, index) => {
ids.push(parseInt(id)) ids.push(parseInt(id))
sorts.push(index) sorts.push(index)
items.value[index].sort_num = index
}) })
httpPost("/api/admin/model/sort", {ids: ids, sorts: sorts}).catch(e => { httpPost("/api/admin/model/sort", {ids: ids, sorts: sorts}).then(() => {
}).catch(e => {
ElMessage.error("排序失败:" + e.message) ElMessage.error("排序失败:" + e.message)
}) })
} }
@ -148,7 +172,7 @@ onMounted(() => {
const add = function () { const add = function () {
title.value = "新增模型" title.value = "新增模型"
showDialog.value = true showDialog.value = true
item.value = {} item.value = {enabled: true, weight: 1}
} }
const edit = function (row) { const edit = function (row) {
@ -197,6 +221,7 @@ const remove = function (row) {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import "@/assets/css/admin-form.styl"
.list { .list {
.opt-box { .opt-box {

View File

@ -87,6 +87,38 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="默认AI模型" prop="default_models">
<template #default>
<div class="tip-input">
<el-select
v-model="system['default_models']"
multiple
:filterable="true"
placeholder="选择AI模型多选"
style="width: 100%"
>
<el-option
v-for="item in models"
:key="item.id"
:label="item.name"
:value="item.value"
/>
</el-select>
<div class="info">
<el-tooltip
class="box-item"
effect="dark"
content="新用户注册默认开通的 AI 模型"
placement="right"
>
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</div>
</div>
</template>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="save('system')">保存</el-button> <el-button type="primary" @click="save('system')">保存</el-button>
</el-form-item> </el-form-item>
@ -198,6 +230,7 @@ const chat = ref({
const loading = ref(true) const loading = ref(true)
const systemFormRef = ref(null) const systemFormRef = ref(null)
const chatFormRef = ref(null) const chatFormRef = ref(null)
const models = ref([])
onMounted(() => { onMounted(() => {
// //
@ -233,6 +266,12 @@ onMounted(() => {
ElMessage.error("加载聊天配置失败: " + e.message) ElMessage.error("加载聊天配置失败: " + e.message)
}) })
httpGet('/api/admin/model/list').then(res => {
models.value = res.data
}).catch(e => {
ElMessage.error("获取模型失败:" + e.message)
})
}) })
const rules = reactive({ const rules = reactive({
@ -293,6 +332,7 @@ const uploadRewardImg = (file) => {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import "@/assets/css/admin-form.styl"
.system-config { .system-config {
display flex display flex
justify-content center justify-content center

View File

@ -104,6 +104,23 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="模型权限" prop="chat_models">
<el-select
v-model="user.chat_models"
multiple
:filterable="true"
placeholder="选择AI模型多选"
>
<el-option
v-for="item in models"
:key="item.id"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="启用状态"> <el-form-item label="启用状态">
<el-switch v-model="user.status"/> <el-switch v-model="user.status"/>
</el-form-item> </el-form-item>
@ -155,9 +172,10 @@ const query = ref({username: '', mobile: '', page: 1, page_size: 15})
const title = ref('添加用户') const title = ref('添加用户')
const add = ref(true) const add = ref(true)
const user = ref({chat_roles: []}) const user = ref({chat_roles: [], chat_models: []})
const pass = ref({username: '', password: '', id: 0}) const pass = ref({username: '', password: '', id: 0})
const roles = ref([]) const roles = ref([])
const models = ref([])
const showUserEditDialog = ref(false) const showUserEditDialog = ref(false)
const showResetPassDialog = ref(false) const showResetPassDialog = ref(false)
const rules = reactive({ const rules = reactive({
@ -170,6 +188,7 @@ const rules = reactive({
{type: 'number', message: '请输入有效数字'}, {type: 'number', message: '请输入有效数字'},
], ],
chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}], chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}],
chat_models: [{required: true, message: '请选择AI模型', trigger: 'change'}],
}) })
const loading = ref(true) const loading = ref(true)
@ -183,6 +202,12 @@ onMounted(() => {
}).catch(() => { }).catch(() => {
ElMessage.error("获取聊天角色失败"); ElMessage.error("获取聊天角色失败");
}) })
httpGet('/api/admin/model/list').then(res => {
models.value = res.data
}).catch(e => {
ElMessage.error("获取模型失败:" + e.message)
})
}) })
const fetchUserList = function (page, pageSize) { const fetchUserList = function (page, pageSize) {