merge v4.0.8

This commit is contained in:
RockYang
2024-08-01 18:09:00 +08:00
76 changed files with 3392 additions and 1940 deletions

View File

@@ -119,12 +119,12 @@ const mjModels = ref([
{name: "急速Turbo", value: "turbo"},
])
httpGet("/api/admin/config/get/draw").then(res => {
httpGet("/api/admin/config/get/app").then(res => {
sdConfigs.value = res.data.sd
mjPlusConfigs.value = res.data.mj_plus
mjProxyConfigs.value = res.data.mj_proxy
}).catch(e =>{
ElMessage.error("获取AI绘画配置失败:"+e.message)
ElMessage.error("获取配置失败:"+e.message)
})
const addConfig = (configs) => {

View File

@@ -2,6 +2,24 @@
<div class="container list" v-loading="loading">
<div class="handle-box">
<el-select v-model="query.platform" placeholder="平台" class="handle-input">
<el-option
v-for="item in platforms"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
<el-select v-model="query.type" placeholder="类型" class="handle-input">
<el-option
v-for="item in types"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
<el-button :icon="Search" @click="fetchData">搜索</el-button>
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
<a href="https://api.chat-plus.net" target="_blank" style="margin-left: 10px">
<el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
@@ -87,7 +105,7 @@
<el-input v-model="item.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="用途" prop="type">
<el-select v-model="item.type" placeholder="请选择用途" @change="changePlatform">
<el-select v-model="item.type" placeholder="请选择用途" @change="changeType">
<el-option v-for="item in types" :value="item.value" :label="item.name" :key="item.value">{{
item.name
}}
@@ -99,7 +117,8 @@
</el-form-item>
<el-form-item label="API URL" prop="api_url">
<el-input v-model="item.api_url" autocomplete="off"
placeholder="如果你用了第三方的 API 中转这里填写中转地址"/>
placeholder="必须填土完整的 Chat API URLhttps://api.openai.com/v1/chat/completions"/>
<div class="info">如果你使用了第三方中转这里就填写中转地址</div>
</el-form-item>
<el-form-item label="代理地址:" prop="proxy_url">
@@ -126,11 +145,12 @@ import {onMounted, onUnmounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
import {DocumentCopy, Plus, ShoppingCart} from "@element-plus/icons-vue";
import {DocumentCopy, Plus, ShoppingCart, Search} from "@element-plus/icons-vue";
import ClipboardJS from "clipboard";
// 变量定义
const items = ref([])
const query = ref({type: '',platform:''})
const item = ref({})
const showDialog = ref(false)
const rules = reactive({
@@ -142,39 +162,7 @@ const rules = reactive({
const loading = ref(true)
const formRef = ref(null)
const title = ref("")
const platforms = ref([
{
name: "OpenAI/中转ChatGPT",
value: "OpenAI",
api_url: "https://api.chat-plus.net/v1/chat/completions",
img_url: "https://api.chat-plus.net/v1/images/generations"
},
{
name: "【讯飞】星火大模型",
value: "XunFei",
api_url: "wss://spark-api.xf-yun.com/{version}/chat"
},
{
name: "【清华智普】ChatGLM",
value: "ChatGLM",
api_url: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke"
},
{
name: "【百度】文心一言",
value: "Baidu",
api_url: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}"
},
{
name: "【微软】Azure",
value: "Azure",
api_url: "https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15"
},
{
name: "【阿里】千义通问",
value: "QWen",
api_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
},
])
const platforms = ref([])
const types = ref([
{name: "聊天", value: "chat"},
{name: "绘画", value: "img"},
@@ -191,6 +179,14 @@ onMounted(() => {
clipboard.value.on('error', () => {
ElMessage.error('复制失败!');
})
httpGet("/api/admin/config/get/app").then(res => {
platforms.value = res.data.platforms
}).catch(e =>{
ElMessage.error("获取配置失败:"+e.message)
})
fetchData()
})
onUnmounted(() => {
@@ -198,19 +194,22 @@ onUnmounted(() => {
})
// 获取数据
httpGet('/api/admin/apikey/list').then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data;
for (let i = 0; i < arr.length; i++) {
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
const fetchData = () => {
httpGet('/api/admin/apikey/list', query.value).then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data;
for (let i = 0; i < arr.length; i++) {
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
}
items.value = arr
}
items.value = arr
}
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
}
const add = function () {
showDialog.value = true
@@ -263,21 +262,24 @@ const set = (filed, row) => {
})
}
const changePlatform = () => {
let platform = null
const selectedPlatform = ref(null)
const changePlatform = (value) => {
console.log(value)
for (let v of platforms.value) {
if (v.value === item.value.platform) {
platform = v
break
if (v.value === value) {
selectedPlatform.value = v
item.value.api_url = v.chat_url
}
}
if (platform !== null) {
if (item.value.type === "img" && platform.img_url) {
item.value.api_url = platform.img_url
} else {
item.value.api_url = platform.api_url
}
}
const changeType = (value) => {
if (selectedPlatform.value) {
if(value === 'img') {
item.value.api_url = selectedPlatform.value.img_url
} else {
item.value.api_url = selectedPlatform.value.chat_url
}
}
}
</script>
@@ -286,6 +288,10 @@ const changePlatform = () => {
.list {
.handle-box {
margin-bottom 20px
.handle-input {
max-width 150px;
margin-right 10px;
}
}
.copy-key {
@@ -306,7 +312,9 @@ const changePlatform = () => {
.el-form {
.el-form-item__content {
.info {
color #999999
}
.el-icon {
padding-left: 10px;
}

View File

@@ -9,25 +9,25 @@
<template #default="props">
<div>
<el-table :data="props.row.context" :border="childBorder">
<el-table-column label="对话角色" prop="role" width="120"/>
<el-table-column label="对话应用" prop="role" width="120"/>
<el-table-column label="对话内容" prop="content"/>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column label="角色名称" prop="name">
<el-table-column label="应用名称" prop="name">
<template #default="scope">
<span class="sort" :data-id="scope.row.id">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="角色标识" prop="key"/>
<el-table-column label="应用标识" prop="key"/>
<el-table-column label="绑定模型" prop="model_name"/>
<el-table-column label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row['enable']" @change="roleSet('enable',scope.row)"/>
</template>
</el-table-column>
<el-table-column label="角色图标" prop="icon">
<el-table-column label="应用图标" prop="icon">
<template #default="scope">
<el-image :src="scope.row.icon" style="width: 45px; height: 45px; border-radius: 50%"/>
</template>
@@ -36,7 +36,7 @@
<el-table-column label="操作" width="150" align="right">
<template #default="scope">
<el-button size="small" type="primary" @click="rowEdit(scope.$index, scope.row)">编辑</el-button>
<el-popconfirm title="确定要删除当前角色吗?" @confirm="removeRole(scope.row)" :width="200">
<el-popconfirm title="确定要删除当前应用吗?" @confirm="removeRole(scope.row)" :width="200">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
@@ -53,21 +53,21 @@
width="50%"
>
<el-form :model="role" label-width="120px" ref="formRef" label-position="left" :rules="rules">
<el-form-item label="角色名称:" prop="name">
<el-form-item label="应用名称:" prop="name">
<el-input
v-model="role.name"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="角色标志:" prop="key">
<el-form-item label="应用标志:" prop="key">
<el-input
v-model="role.key"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="角色图标:" prop="icon">
<el-form-item label="应用图标:" prop="icon">
<el-input v-model="role.icon">
<template #append>
<el-upload
@@ -107,7 +107,7 @@
<el-form-item label="上下文信息:" prop="context">
<template #default>
<el-table :data="role.context" :border="childBorder" size="small">
<el-table-column label="对话角色" width="120">
<el-table-column label="对话应用" width="120">
<template #default="scope">
<el-input
v-model="scope.row.role"
@@ -133,6 +133,8 @@
<template #default="scope">
<div class="context-msg-content">
<el-input
type="textarea"
:rows="2"
v-model="scope.row.content"
autocomplete="off"
/>
@@ -181,8 +183,8 @@ const loading = ref(true)
const rules = reactive({
name: [{required: true, message: '请输入用户名', trigger: 'blur',}],
key: [{required: true, message: '请输入角色标识', trigger: 'blur',}],
icon: [{required: true, message: '请输入角色图标', trigger: 'blur',}],
key: [{required: true, message: '请输入应用标识', trigger: 'blur',}],
icon: [{required: true, message: '请输入应用图标', trigger: 'blur',}],
sort: [
{required: true, message: '请输入排序数字', trigger: 'blur'},
{type: 'number', message: '请输入有效数字'},
@@ -204,13 +206,13 @@ onMounted(() => {
})
const fetchData = () => {
//
//
httpGet('/api/admin/role/list').then((res) => {
tableData.value = res.data
sortedTableData.value = copyObj(tableData.value)
loading.value = false
}).catch(() => {
ElMessage.error("获取聊天角色失败");
ElMessage.error("获取聊天应用失败");
})
const drawBodyWrapper = document.querySelector('.el-table__body tbody')
@@ -250,14 +252,14 @@ const roleSet = (filed, row) => {
//
const curIndex = ref(0)
const rowEdit = function (index, row) {
optTitle.value = "修改角色"
optTitle.value = "修改应用"
curIndex.value = index
role.value = copyObj(row)
showDialog.value = true
}
const addRole = function () {
optTitle.value = "添加新角色"
optTitle.value = "添加新应用"
role.value = {context: []}
showDialog.value = true
}

View File

@@ -163,11 +163,8 @@
:model="item.model"
:content="item.content"/>
<chat-reply v-else-if="item.type==='reply'"
:icon="item.icon"
:org-content="item.orgContent"
:created-at="dateFormat(item['created_at'])"
:tokens="item['tokens']"
:content="item.content"/>
:read-only="true"
:data="item"/>
</div>
</div><!-- end chat box -->
</el-dialog>
@@ -291,8 +288,7 @@ const removeMessage = function (row) {
})
}
const latexPlugin = require('markdown-it-latex2img')
const mathjaxPlugin = require('markdown-it-mathjax')
const mathjaxPlugin = require('markdown-it-mathjax3')
const md = require('markdown-it')({
breaks: true,
html: true,
@@ -312,7 +308,6 @@ const md = require('markdown-it')({
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
}
});
md.use(latexPlugin)
md.use(mathjaxPlugin)
const showContentDialog = ref(false)

View File

@@ -2,6 +2,16 @@
<div class="container model-list" v-loading="loading">
<div class="handle-box">
<el-select v-model="query.platform" placeholder="平台" class="handle-input">
<el-option
v-for="item in platforms"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
<el-button :icon="Search" @click="fetchData">搜索</el-button>
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
</div>
@@ -198,12 +208,13 @@ import {onMounted, onUnmounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
import {DocumentCopy, InfoFilled, Plus} from "@element-plus/icons-vue";
import {DocumentCopy, InfoFilled, Plus,Search} from "@element-plus/icons-vue";
import {Sortable} from "sortablejs";
import ClipboardJS from "clipboard";
// 变量定义
const items = ref([])
const query = ref({platform:''})
const item = ref({})
const showDialog = ref(false)
const title = ref("")
@@ -214,15 +225,7 @@ const rules = reactive({
})
const loading = ref(true)
const formRef = ref(null)
const platforms = ref([
{name: "OpenAIChatGPT", value: "OpenAI"},
{name: "讯飞星火大模型", value: "XunFei"},
{name: "清华智普ChatGLM", value: "ChatGLM"},
{name: "百度文心一言", value: "Baidu"},
{name: "微软Azure", value: "Azure"},
{name: "阿里通义千问", value: "QWen"},
])
const platforms = ref([])
// 获取 API KEY
const apiKeys = ref([])
@@ -234,7 +237,7 @@ httpGet('/api/admin/apikey/list?status=true&type=chat').then(res => {
// 获取数据
const fetchData = () => {
httpGet('/api/admin/model/list').then((res) => {
httpGet('/api/admin/model/list', query.value).then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data;
@@ -287,6 +290,12 @@ onMounted(() => {
clipboard.value.on('error', () => {
ElMessage.error('复制失败!');
})
httpGet("/api/admin/config/get/app").then(res => {
platforms.value = res.data.platforms
}).catch(e =>{
ElMessage.error("获取配置失败"+e.message)
})
})
onUnmounted(() => {
@@ -352,6 +361,10 @@ const remove = function (row) {
.handle-box {
margin-bottom 20px
.handle-input {
max-width 150px;
margin-right 10px;
}
}
.cell {

View File

@@ -44,6 +44,20 @@
</el-tooltip>
</el-form-item>
<el-form-item label="随机背景">
<el-switch v-model="system['rand_bg']"/>
<el-tooltip
effect="dark"
content="打开之后前端首页将使用随机壁纸作为背景图"
raw-content
placement="right"
>
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</el-form-item>
<el-form-item label="注册方式" prop="register_ways">
<el-checkbox-group v-model="system['register_ways']">
<el-checkbox value="mobile">手机注册</el-checkbox>
@@ -298,16 +312,22 @@
<el-descriptions
v-if="license.is_active"
class="margin-top"
title="授权信息"
:column="3"
title="授权信息"
:column="1"
border
>
<el-descriptions-item :span="3" :width="150">
<el-descriptions-item>
<template #label>
<div class="cell-item">License Key</div>
</template>
{{ license.key }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">机器码</div>
</template>
{{ license.machine_id }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">到期时间</div>
@@ -318,13 +338,15 @@
<template #label>
<div class="cell-item">用户人数</div>
</template>
{{ license.user_num }}
{{ license.configs?.user_num }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">机器码</div>
<div class="cell-item">去版权</div>
</template>
{{ license.machine_id }}
<el-icon class="selected" v-if="license.configs?.de_copy"><Select /></el-icon>
<el-icon class="closed" v-else><CloseBold /></el-icon>
<span class="text">去版权之后前端页面将不会显示版权信息和源码地址</span>
</el-descriptions-item>
</el-descriptions>
@@ -348,7 +370,7 @@ import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import Compressor from "compressorjs";
import {ElMessage} from "element-plus";
import {InfoFilled, UploadFilled} from "@element-plus/icons-vue";
import {InfoFilled, UploadFilled,Select,CloseBold} from "@element-plus/icons-vue";
import MdEditor from "md-editor-v3";
import 'md-editor-v3/lib/style.css';
import Menu from "@/views/admin/Menu.vue";
@@ -536,6 +558,23 @@ const onUploadImg = (files, callback) => {
.el-descriptions {
margin-bottom 20px
.el-icon {
font-size 18px
}
.selected {
color #0bc15f
}
.closed {
color #da0d54
}
.text {
margin-left 10px
font-size 12px
color #999999
position: relative;
top -5px
}
}
.el-alert {

View File

@@ -354,6 +354,8 @@ const doResetPass = () => {
.pagination {
padding 20px 0
display flex
justify-content right
width 100%
}
.el-select {