mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-09 02:33:42 +08:00
merge v4.0.8
This commit is contained in:
@@ -6,4 +6,4 @@ VUE_APP_ADMIN_USER=admin
|
||||
VUE_APP_ADMIN_PASS=admin123
|
||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
|
||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||
VUE_APP_VERSION=v4.0.7
|
||||
VUE_APP_VERSION=v4.0.8
|
||||
|
||||
@@ -2,4 +2,4 @@ VUE_APP_API_HOST=
|
||||
VUE_APP_WS_HOST=
|
||||
VUE_APP_KEY_PREFIX=ChatPLUS_
|
||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||
VUE_APP_VERSION=v4.0.7
|
||||
VUE_APP_VERSION=v4.0.8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "chatgpt-plus-web",
|
||||
"name": "geekai-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -20,8 +20,7 @@
|
||||
"json-bigint": "^1.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-latex2img": "^0.0.6",
|
||||
"markdown-it-mathjax": "^2.0.0",
|
||||
"markdown-it-mathjax3": "^4.3.2",
|
||||
"markmap-common": "^0.16.0",
|
||||
"markmap-lib": "^0.16.1",
|
||||
"markmap-toolbar": "^0.17.0",
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
.item {
|
||||
display flex
|
||||
flex-flow row
|
||||
border 1px solid #3c3c3c
|
||||
border 1px solid rgb(80,80,80)
|
||||
padding 10px
|
||||
background rgba(60,60,60 0.5)
|
||||
|
||||
.image {
|
||||
width 80px
|
||||
|
||||
@@ -129,15 +129,44 @@ $borderColor = #4676d0;
|
||||
--el-main-padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.chat-box {
|
||||
.chat-container {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
background-color: var(--el-bg-color)
|
||||
color var(--el-text-color-primary)
|
||||
|
||||
.chat-config {
|
||||
height 30px
|
||||
padding 10px 30px
|
||||
display flex
|
||||
justify-content center
|
||||
justify-items center
|
||||
border-bottom 1px solid #d9d9e3
|
||||
|
||||
.role-select-label {
|
||||
color #ffffff
|
||||
}
|
||||
|
||||
.el-select {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
}
|
||||
|
||||
.role-select {
|
||||
max-width 130px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
.el-icon {
|
||||
margin-right 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
position relative
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
@@ -165,68 +194,97 @@ $borderColor = #4676d0;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
background-color: #ffffff
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
padding 0 15px;
|
||||
position absolute
|
||||
bottom 0
|
||||
width 100%
|
||||
|
||||
.tool-item {
|
||||
margin-right 15px
|
||||
border-radius: 6px;
|
||||
color: #19c37d;
|
||||
.input-box-inner {
|
||||
display flex
|
||||
justify-content center
|
||||
justify-items center
|
||||
padding 6px
|
||||
cursor pointer
|
||||
background #F2F2F2
|
||||
background-color: #ffffff
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
padding 0 15px;
|
||||
|
||||
&:hover {
|
||||
background #D5FAD3
|
||||
}
|
||||
.tool-item {
|
||||
margin-right 15px
|
||||
border-radius: 6px;
|
||||
color: #19c37d;
|
||||
display flex
|
||||
justify-content center
|
||||
justify-items center
|
||||
padding 6px
|
||||
cursor pointer
|
||||
background #F2F2F2
|
||||
|
||||
.iconfont {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background #D5FAD3
|
||||
}
|
||||
|
||||
.input-container {
|
||||
width 100%
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 10px 0;
|
||||
display flex
|
||||
justify-content center
|
||||
position relative
|
||||
|
||||
.el-textarea {
|
||||
.el-textarea__inner::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
.iconfont {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-file {
|
||||
position absolute;
|
||||
right 48px;
|
||||
top 20px;
|
||||
}
|
||||
.input-body {
|
||||
width 100%
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 10px 0;
|
||||
display flex
|
||||
justify-content center
|
||||
position relative
|
||||
|
||||
.send-btn {
|
||||
position absolute;
|
||||
right 12px;
|
||||
top 20px;
|
||||
.hide-div {
|
||||
white-space: pre-wrap; /* 保持文本换行 */
|
||||
visibility: hidden; /* 隐藏 div */
|
||||
position: absolute; /* 脱离文档流 */
|
||||
line-height: 24px
|
||||
font-size 14px
|
||||
word-wrap: break-word; /* 允许单词换行 */
|
||||
overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
|
||||
}
|
||||
|
||||
.input-border {
|
||||
display flex
|
||||
width 100%
|
||||
overflow hidden
|
||||
border: 2px solid #21AA93
|
||||
border-radius 10px
|
||||
padding 10px
|
||||
|
||||
|
||||
.prompt-input::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
width 100%
|
||||
line-height: 24px
|
||||
border none
|
||||
font-size 14px
|
||||
background none
|
||||
resize: none
|
||||
white-space: pre-wrap; /* 保持文本换行 */
|
||||
word-wrap: break-word; /* 允许单词换行 */
|
||||
overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
width 32px
|
||||
margin-left 10px
|
||||
.el-button {
|
||||
padding 8px 5px;
|
||||
border-radius 6px;
|
||||
font-size 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
padding 8px 5px;
|
||||
border-radius 6px;
|
||||
font-size 20px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
cursor pointer
|
||||
|
||||
.el-image {
|
||||
width 40px
|
||||
height 40px
|
||||
width 48px
|
||||
height 48px
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
.navigator {
|
||||
display flex
|
||||
flex-flow column
|
||||
width 50px
|
||||
width 60px
|
||||
padding 10px 1px
|
||||
border-right: 1px solid #3c3c3c
|
||||
background-color: #1E1F22
|
||||
@@ -97,12 +97,14 @@
|
||||
|
||||
li {
|
||||
margin-bottom 15px
|
||||
display flex
|
||||
flex-flow column
|
||||
|
||||
a {
|
||||
color #DADBDC
|
||||
border-radius 10px
|
||||
width 40px
|
||||
height 40px
|
||||
width 48px
|
||||
height 48px
|
||||
display flex
|
||||
justify-content center
|
||||
align-items center
|
||||
@@ -125,9 +127,12 @@
|
||||
|
||||
.title {
|
||||
font-size: 12px
|
||||
padding-top: 5px
|
||||
padding-top: 6px
|
||||
color: #e5e7eb;
|
||||
text-align: center;
|
||||
white-space: nowrap; /* 防止文本换行 */
|
||||
overflow: hidden; /* 隐藏溢出内容 */
|
||||
text-overflow: unset; /* 使用省略号表示溢出内容 */
|
||||
}
|
||||
|
||||
.active {
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
display flex
|
||||
cursor pointer
|
||||
margin-bottom: 10px;
|
||||
border 1px solid #383838
|
||||
|
||||
&:hover {
|
||||
background-color #585858
|
||||
@@ -61,60 +62,9 @@
|
||||
margin-bottom 5px
|
||||
}
|
||||
|
||||
.shape {
|
||||
width 16px
|
||||
height 16px
|
||||
margin-bottom 5px
|
||||
border 1px solid #C4C4C4
|
||||
border-radius 3px
|
||||
}
|
||||
|
||||
.shape.size9-16 {
|
||||
width 9px
|
||||
height 16px
|
||||
}
|
||||
|
||||
.shape.size16-9 {
|
||||
height 9px
|
||||
width 16px
|
||||
position relative
|
||||
margin 4px 0 7px
|
||||
}
|
||||
|
||||
.shape.size3-4 {
|
||||
width 12px
|
||||
height 16px
|
||||
}
|
||||
|
||||
.shape.size4-3 {
|
||||
height 12px
|
||||
width 16px
|
||||
position relative
|
||||
margin 4px 0 4px
|
||||
}
|
||||
|
||||
.shape.size2-3 {
|
||||
width 11px
|
||||
height 16px
|
||||
}
|
||||
|
||||
.shape.size3-2 {
|
||||
height 11px
|
||||
width 16px
|
||||
position relative
|
||||
margin 4px 0 5px
|
||||
}
|
||||
|
||||
.shape.size1-2 {
|
||||
width 8px
|
||||
height 16px
|
||||
}
|
||||
|
||||
.shape.size2-1 {
|
||||
height 8px
|
||||
width 16px
|
||||
position relative
|
||||
margin 4px 0 8px
|
||||
.text {
|
||||
margin-left 5px
|
||||
margin-top 2px
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,10 +72,7 @@
|
||||
.grid-content.active {
|
||||
color #47fff1
|
||||
background-color #585858
|
||||
|
||||
.shape {
|
||||
border 1px solid #47fff1
|
||||
}
|
||||
border 1px solid #47fff1
|
||||
}
|
||||
|
||||
.model {
|
||||
|
||||
@@ -92,9 +92,9 @@ const items = [
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'role',
|
||||
index: '/admin/role',
|
||||
title: '角色管理',
|
||||
icon: 'menu',
|
||||
index: '/admin/app',
|
||||
title: '应用管理',
|
||||
},
|
||||
{
|
||||
icon: 'api-key',
|
||||
|
||||
@@ -46,7 +46,7 @@ const routes = [
|
||||
component: () => import('@/views/Member.vue'),
|
||||
},
|
||||
{
|
||||
name: 'chat-role',
|
||||
name: 'chat-app',
|
||||
path: '/apps',
|
||||
meta: {title: '应用中心'},
|
||||
component: () => import('@/views/ChatApps.vue'),
|
||||
@@ -139,10 +139,10 @@ const routes = [
|
||||
component: () => import('@/views/admin/Users.vue'),
|
||||
},
|
||||
{
|
||||
path: '/admin/role',
|
||||
name: 'admin-role',
|
||||
meta: {title: '角色管理'},
|
||||
component: () => import('@/views/admin/Roles.vue'),
|
||||
path: '/admin/app',
|
||||
name: 'admin-app',
|
||||
meta: {title: '应用管理'},
|
||||
component: () => import('@/views/admin/Apps.vue'),
|
||||
},
|
||||
{
|
||||
path: '/admin/apikey',
|
||||
|
||||
@@ -65,9 +65,44 @@
|
||||
</div>
|
||||
</el-aside>
|
||||
<el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)">
|
||||
<div class="chat-box" :style="{height: mainWinHeight+'px'}">
|
||||
<div class="chat-container">
|
||||
<div class="chat-config">
|
||||
<el-select v-model="roleId" filterable placeholder="角色" @change="_newChat"
|
||||
class="role-select"
|
||||
style="width:150px">
|
||||
<el-option
|
||||
v-for="item in roles"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<div class="role-option">
|
||||
<el-image :src="item.icon"></el-image>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="modelID" filterable placeholder="模型" @change="_newChat"
|
||||
:disabled="disableModel"
|
||||
style="width:150px">
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
<el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
|
||||
item.power
|
||||
}}算力
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div id="container">
|
||||
<div id="container" :style="{height: mainWinHeight+'px'}">
|
||||
<div class="chat-box" id="chat-box" :style="{height: chatBoxHeight+'px'}">
|
||||
<div v-if="showHello">
|
||||
<welcome @send="autofillPrompt"/>
|
||||
@@ -84,61 +119,8 @@
|
||||
</div>
|
||||
</div><!-- end chat box -->
|
||||
|
||||
<el-affix position="bottom" :offset="0">
|
||||
<div class="input-box">
|
||||
<span class="tool-item">
|
||||
<el-popover
|
||||
:width="300"
|
||||
trigger="click"
|
||||
placement="top-start"
|
||||
>
|
||||
<template #reference>
|
||||
<div>
|
||||
<el-tooltip effect="dark" content="模型选择">
|
||||
<i class="iconfont icon-model"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div class="chat-config">
|
||||
<el-select v-model="roleId" filterable placeholder="角色" @change="_newChat"
|
||||
class="role-select"
|
||||
style="width:150px">
|
||||
<el-option
|
||||
v-for="item in roles"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<div class="role-option">
|
||||
<el-image :src="item.icon"></el-image>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="modelID" filterable placeholder="模型" @change="_newChat"
|
||||
:disabled="disableModel"
|
||||
style="width:150px">
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
<el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
|
||||
item.power
|
||||
}}算力
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<div class="input-box">
|
||||
<div class="input-box-inner">
|
||||
<span class="tool-item" @click="ElMessage.info('暂时不支持语音输入')">
|
||||
<el-tooltip class="box-item" effect="dark" content="语音输入">
|
||||
<i class="iconfont icon-mic-bold"></i>
|
||||
@@ -151,32 +133,34 @@
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
||||
<div class="input-container">
|
||||
<el-input
|
||||
ref="textInput"
|
||||
v-model="prompt"
|
||||
v-on:keydown="inputKeyDown"
|
||||
autofocus
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
style="--el-input-focus-border-color:#21AA93;
|
||||
border: 1px solid #21AA93;--el-input-border-color:#21AA93;
|
||||
border-radius: 5px; --el-input-hover-border-color:#21AA93;"
|
||||
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
||||
/>
|
||||
<span class="send-btn">
|
||||
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
||||
<el-icon>
|
||||
<VideoPause/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button @click="sendMessage" color="#19c37d" style="color:#ffffff" v-else>
|
||||
<el-icon><Promotion/></el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
<div class="input-body">
|
||||
<div ref="textHeightRef" class="hide-div">{{prompt}}</div>
|
||||
<div class="input-border">
|
||||
<textarea
|
||||
ref="inputRef"
|
||||
class="prompt-input"
|
||||
:rows="row"
|
||||
v-model="prompt"
|
||||
@keydown="onInput"
|
||||
@input="onInput"
|
||||
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
||||
autofocus>
|
||||
</textarea>
|
||||
<span class="send-btn">
|
||||
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
||||
<el-icon>
|
||||
<VideoPause/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button @click="sendMessage" color="#19c37d" style="color:#ffffff" v-else>
|
||||
<el-icon><Promotion/></el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div><!-- end input box -->
|
||||
</el-affix>
|
||||
</div>
|
||||
</div><!-- end container -->
|
||||
</div><!-- end loading -->
|
||||
</div>
|
||||
@@ -237,11 +221,13 @@ const roleId = ref(0)
|
||||
const newChatItem = ref(null);
|
||||
const isLogin = ref(false)
|
||||
const showHello = ref(true)
|
||||
const textInput = ref(null)
|
||||
const inputRef = ref(null)
|
||||
const textHeightRef = ref(null)
|
||||
const showNotice = ref(false)
|
||||
const notice = ref("")
|
||||
const noticeKey = ref("SYSTEM_NOTICE")
|
||||
const store = useSharedStore();
|
||||
const row = ref(1)
|
||||
|
||||
if (isMobile()) {
|
||||
router.replace("/mobile/chat")
|
||||
@@ -372,8 +358,8 @@ const getRoleById = function (rid) {
|
||||
}
|
||||
|
||||
const resizeElement = function () {
|
||||
chatBoxHeight.value = window.innerHeight - 50 - 82 - 38;
|
||||
mainWinHeight.value = window.innerHeight - 50;
|
||||
chatBoxHeight.value = window.innerHeight - 101 - 82 - 38;
|
||||
mainWinHeight.value = window.innerHeight - 101;
|
||||
leftBoxHeight.value = window.innerHeight - 90 - 45 - 82;
|
||||
};
|
||||
|
||||
@@ -517,8 +503,7 @@ const removeChat = function (chat) {
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -543,7 +528,6 @@ const md = require('markdown-it')({
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
|
||||
}
|
||||
});
|
||||
md.use(latexPlugin)
|
||||
md.use(mathjaxPlugin)
|
||||
|
||||
// 创建 socket 连接
|
||||
@@ -705,8 +689,19 @@ const enableInput = () => {
|
||||
showStopGenerate.value = false;
|
||||
}
|
||||
|
||||
// 登录输入框输入事件处理
|
||||
const inputKeyDown = function (e) {
|
||||
const onInput = (e) => {
|
||||
// 根据输入的内容自动计算输入框的行数
|
||||
const lineHeight = parseFloat(window.getComputedStyle(inputRef.value).lineHeight)
|
||||
textHeightRef.value.style.width = inputRef.value.clientWidth + 'px'; // 设定宽度和 textarea 相同
|
||||
const lines = Math.floor(textHeightRef.value.clientHeight / lineHeight);
|
||||
inputRef.value.scrollTo(0, inputRef.value.scrollHeight)
|
||||
if (prompt.value.length < 10) {
|
||||
row.value = 1
|
||||
} else if (row.value <= 7) {
|
||||
row.value = lines
|
||||
}
|
||||
|
||||
// 输入回车自动提交
|
||||
if (e.keyCode === 13) {
|
||||
if (e.ctrlKey) { // Ctrl + Enter 换行
|
||||
prompt.value += "\n";
|
||||
@@ -720,7 +715,7 @@ const inputKeyDown = function (e) {
|
||||
// 自动填充 prompt
|
||||
const autofillPrompt = (text) => {
|
||||
prompt.value = text
|
||||
textInput.value.focus()
|
||||
inputRef.value.focus()
|
||||
// sendMessage()
|
||||
}
|
||||
// 发送消息
|
||||
@@ -929,29 +924,4 @@ const insertURL = (url) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-config {
|
||||
display flex
|
||||
flex-direction row
|
||||
padding-top 10px;
|
||||
|
||||
.role-select-label {
|
||||
color #ffffff
|
||||
}
|
||||
|
||||
.el-select {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
}
|
||||
|
||||
.role-select {
|
||||
max-width 130px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
.el-icon {
|
||||
margin-right 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,8 +11,8 @@
|
||||
</div>
|
||||
|
||||
<div class="navbar">
|
||||
|
||||
<el-tooltip
|
||||
v-if="!licenseConfig.de_copy"
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="部署文档"
|
||||
@@ -23,6 +23,7 @@
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
v-if="!licenseConfig.de_copy"
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="项目源码"
|
||||
@@ -85,6 +86,7 @@
|
||||
<el-image :src="item.icon" style="width: 30px;height: 30px"/>
|
||||
</a>
|
||||
</el-tooltip>
|
||||
<span :class="item.url === curPath ? 'title active' : 'title'">{{ item.name }}</span>
|
||||
</li>
|
||||
|
||||
<el-popover
|
||||
@@ -139,6 +141,7 @@ import {removeUserToken} from "@/store/session";
|
||||
import LoginDialog from "@/components/LoginDialog.vue";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import ConfigDialog from "@/components/ConfigDialog.vue";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
|
||||
const router = useRouter();
|
||||
const logo = ref('/images/logo.png');
|
||||
@@ -151,6 +154,7 @@ const loginUser = ref({})
|
||||
const version = ref(process.env.VUE_APP_VERSION)
|
||||
const routerViewKey = ref(0)
|
||||
const showConfigDialog = ref(false)
|
||||
const licenseConfig = ref({})
|
||||
|
||||
const store = useSharedStore();
|
||||
const show = ref(false)
|
||||
@@ -190,6 +194,12 @@ onMounted(() => {
|
||||
ElMessage.error("获取系统菜单失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/config/license").then(res => {
|
||||
licenseConfig.value = res.data
|
||||
}).catch(e => {
|
||||
showMessageError("获取 License 配置:" + e.message)
|
||||
})
|
||||
|
||||
init()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="index-page" :style="{height: winHeight+'px'}">
|
||||
<div class="bg"></div>
|
||||
<div :class="bgClass"></div>
|
||||
<div class="menu-box">
|
||||
<el-menu
|
||||
mode="horizontal"
|
||||
@@ -11,7 +11,8 @@
|
||||
<div class="title">{{ title }}</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a href="https://ai.r9it.com/docs/install/" target="_blank">
|
||||
<span v-if="!licenseConfig.de_copy">
|
||||
<a href="https://ai.r9it.com/docs/install/" target="_blank">
|
||||
<el-button type="primary" round>
|
||||
<i class="iconfont icon-book"></i>
|
||||
<span>部署文档</span>
|
||||
@@ -24,6 +25,7 @@
|
||||
<span>项目源码</span>
|
||||
</el-button>
|
||||
</a>
|
||||
</span>
|
||||
<el-button @click="router.push('/login')" round>登录</el-button>
|
||||
<el-button @click="router.push('/register')" round>注册</el-button>
|
||||
</div>
|
||||
@@ -52,7 +54,7 @@
|
||||
<!-- <div id="animation-container"></div>-->
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer" v-if="!licenseConfig.de_copy">
|
||||
<footer-bar />
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,73 +79,31 @@ if (isMobile()) {
|
||||
const title = ref("Geek-AI 创作系统")
|
||||
const logo = ref("/images/logo.png")
|
||||
const slogan = ref("我辈之人,先干为敬,陪您先把 AI 用起来")
|
||||
const licenseConfig = ref({})
|
||||
// const size = Math.max(window.innerWidth * 0.5, window.innerHeight * 0.8)
|
||||
const winHeight = window.innerHeight - 150
|
||||
const bgClass = ref('fixed-bg')
|
||||
|
||||
onMounted(() => {
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
title.value = res.data.title
|
||||
logo.value = res.data.logo
|
||||
if (res.data.rand_bg) {
|
||||
bgClass.value = "rand-bg"
|
||||
}
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/config/license").then(res => {
|
||||
licenseConfig.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取 License 配置:" + e.message)
|
||||
})
|
||||
init()
|
||||
})
|
||||
|
||||
const init = () => {
|
||||
// // 创建场景
|
||||
// // 创建场景
|
||||
// const scene = new THREE.Scene();
|
||||
//
|
||||
// // 创建相机
|
||||
// const camera = new THREE.PerspectiveCamera(30, 1, 0.1, 1000);
|
||||
// camera.position.z = 3.88;
|
||||
//
|
||||
// // 创建渲染器
|
||||
// const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
// renderer.setSize(size, size);
|
||||
// renderer.setClearColor(0x000000, 0);
|
||||
// const container = document.getElementById('animation-container');
|
||||
// container.appendChild(renderer.domElement);
|
||||
//
|
||||
// // 加载地球纹理
|
||||
// const loader = new THREE.TextureLoader();
|
||||
// loader.load(
|
||||
// '/images/land_ocean_ice_cloud_2048.jpg',
|
||||
// function (texture) {
|
||||
// // 创建地球球体
|
||||
// const geometry = new THREE.SphereGeometry(1, 32, 32);
|
||||
// const material = new THREE.MeshPhongMaterial({
|
||||
// map: texture,
|
||||
// bumpMap: texture, // 使用同一张纹理作为凹凸贴图
|
||||
// bumpScale: 0.05, // 调整凹凸贴图的影响程度
|
||||
// specularMap: texture, // 高光贴图
|
||||
// specular: new THREE.Color('#01193B'), // 高光颜色
|
||||
// });
|
||||
// const earth = new THREE.Mesh(geometry, material);
|
||||
// scene.add(earth);
|
||||
//
|
||||
// // 添加环境光和点光源
|
||||
// const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
|
||||
// scene.add(ambientLight);
|
||||
// const pointLight = new THREE.PointLight(0xffffff, 0.8);
|
||||
// pointLight.position.set(5, 5, 5);
|
||||
// scene.add(pointLight);
|
||||
//
|
||||
// // 创建动画
|
||||
// const animate = function () {
|
||||
// requestAnimationFrame(animate);
|
||||
//
|
||||
// // 使地球自转和公转
|
||||
// earth.rotation.y += 0.0006;
|
||||
//
|
||||
// renderer.render(scene, camera);
|
||||
// };
|
||||
//
|
||||
// // 执行动画
|
||||
// animate();
|
||||
// }
|
||||
// );
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -158,14 +118,25 @@ const init = () => {
|
||||
align-items baseline
|
||||
padding-top 150px
|
||||
|
||||
.bg {
|
||||
.fixed-bg {
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100vw
|
||||
height 100vh
|
||||
background-image url("~@/assets/img/ai-bg.jpg")
|
||||
//filter: blur(8px);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.rand-bg {
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100vw
|
||||
height 100vh
|
||||
background-image url("https://api.dujin.org/bing/1920.php")
|
||||
filter: blur(8px);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<reset-pass @hide="showResetPass = false" :show="showResetPass"/>
|
||||
|
||||
<footer class="footer">
|
||||
<footer class="footer" v-if="!licenseConfig.de_copy">
|
||||
<footer-bar/>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -74,6 +74,7 @@ const username = ref(process.env.VUE_APP_USER);
|
||||
const password = ref(process.env.VUE_APP_PASS);
|
||||
const showResetPass = ref(false)
|
||||
const logo = ref("/images/logo.png")
|
||||
const licenseConfig = ref({})
|
||||
|
||||
// 获取系统配置
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
@@ -83,6 +84,11 @@ httpGet("/api/config/get?key=system").then(res => {
|
||||
showMessageError("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/config/license").then(res => {
|
||||
licenseConfig.value = res.data
|
||||
}).catch(e => {
|
||||
showMessageError("获取 License 配置:" + e.message)
|
||||
})
|
||||
|
||||
checkSession().then(() => {
|
||||
if (isMobile()) {
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
</el-result>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<footer class="footer" v-if="!licenseConfig.de_copy">
|
||||
<footer-bar/>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -199,6 +199,7 @@ const enableUser = ref(false)
|
||||
const enableRegister = ref(false)
|
||||
const activeName = ref("mobile")
|
||||
const wxImg = ref("/images/wx.png")
|
||||
const licenseConfig = ref({})
|
||||
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
if (res.data) {
|
||||
@@ -225,6 +226,12 @@ httpGet("/api/config/get?key=system").then(res => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/config/license").then(res => {
|
||||
licenseConfig.value = res.data
|
||||
}).catch(e => {
|
||||
showMessageError("获取 License 配置:" + e.message)
|
||||
})
|
||||
|
||||
// 注册操作
|
||||
const submitRegister = () => {
|
||||
if (data.value.username === '') {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 URL,如:https://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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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: "【OpenAI】ChatGPT", 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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -354,6 +354,8 @@ const doResetPass = () => {
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
width 100%
|
||||
}
|
||||
|
||||
.el-select {
|
||||
|
||||
@@ -62,6 +62,7 @@ const model = ref('')
|
||||
const finished = ref(false)
|
||||
const error = ref(false)
|
||||
|
||||
const mathjaxPlugin = require('markdown-it-mathjax3')
|
||||
const md = require('markdown-it')({
|
||||
breaks: true,
|
||||
html: true,
|
||||
@@ -86,6 +87,7 @@ const md = require('markdown-it')({
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
|
||||
}
|
||||
});
|
||||
md.use(mathjaxPlugin)
|
||||
|
||||
const onLoad = () => {
|
||||
httpGet('/api/chat/history?chat_id=' + chatId).then(res => {
|
||||
@@ -127,7 +129,7 @@ const onLoad = () => {
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="stylus">
|
||||
<style scoped lang="stylus">
|
||||
.chat-export-mobile {
|
||||
height 100vh
|
||||
|
||||
@@ -144,7 +146,7 @@ const onLoad = () => {
|
||||
|
||||
.message-list-box {
|
||||
background #F5F5F5;
|
||||
padding-bottom: 10px
|
||||
padding-bottom: 50px
|
||||
|
||||
.van-cell {
|
||||
background none
|
||||
|
||||
@@ -225,18 +225,6 @@ const newChat = (item) => {
|
||||
}
|
||||
showPicker.value = false
|
||||
const options = item.selectedOptions
|
||||
// setChatConfig({
|
||||
// role: {
|
||||
// id: options[0].value,
|
||||
// name: options[0].text,
|
||||
// icon: options[0].icon,
|
||||
// helloMsg: options[0].helloMsg
|
||||
// },
|
||||
// model: options[1].value,
|
||||
// modelValue: getModelValue(options[1].value),
|
||||
// title: '新建会话',
|
||||
// chatId: 0
|
||||
// })
|
||||
router.push({
|
||||
name: "mobile-chat-session",
|
||||
params: {role_id: options[0].value, model_id: options[1].value, title: '新建会话', chat_id: 0}
|
||||
@@ -244,25 +232,6 @@ const newChat = (item) => {
|
||||
}
|
||||
|
||||
const changeChat = (chat) => {
|
||||
// let role = {}
|
||||
// for (let i = 0; i < roles.value.length; i++) {
|
||||
// if (roles.value[i].value === chat.role_id) {
|
||||
// role = roles.value[i]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// setChatConfig({
|
||||
// role: {
|
||||
// id: chat.role_id,
|
||||
// name: role.text,
|
||||
// icon: role.icon
|
||||
// },
|
||||
// model: chat.model_id,
|
||||
// modelValue: getModelValue(chat.model_id),
|
||||
// title: chat.title,
|
||||
// chatId: chat.chat_id,
|
||||
// helloMsg: chat.hello_msg,
|
||||
// })
|
||||
router.push(`/mobile/chat/session?chat_id=${chat.chat_id}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ httpGet('/api/model/list').then(res => {
|
||||
models.value[i].mValue = models.value[i].value
|
||||
models.value[i].value = models.value[i].id
|
||||
}
|
||||
modelValue.value = getModelValue(modelId.value)
|
||||
modelValue.value = getModelName(modelId.value)
|
||||
// 加载角色列表
|
||||
httpGet(`/api/role/list`).then((res) => {
|
||||
roles.value = res.data;
|
||||
@@ -236,6 +236,7 @@ const newChat = (item) => {
|
||||
const options = item.selectedOptions
|
||||
roleId.value = options[0].value
|
||||
modelId.value = options[1].value
|
||||
modelValue.value = getModelName(modelId.value)
|
||||
chatId.value = ""
|
||||
chatData.value = []
|
||||
role.value = getRoleById(roleId.value)
|
||||
@@ -248,8 +249,7 @@ const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const error = ref(false)
|
||||
|
||||
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,
|
||||
@@ -274,7 +274,6 @@ const md = require('markdown-it')({
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
|
||||
}
|
||||
});
|
||||
md.use(latexPlugin)
|
||||
md.use(mathjaxPlugin)
|
||||
|
||||
|
||||
@@ -537,7 +536,7 @@ const shareChat = (option) => {
|
||||
showToast({message: "当前会话已经导出,请通过浏览器或者微信的自带分享功能分享给好友", duration: 5000})
|
||||
router.push({
|
||||
path: "/mobile/chat/export",
|
||||
query: {title: title, chat_id: chatId, role: role.value.name, model: modelValue}
|
||||
query: {title: title.value, chat_id: chatId.value, role: role.value.name, model: modelValue.value}
|
||||
})
|
||||
} else if (option.icon === "link") {
|
||||
document.getElementById('copy-link-btn').click();
|
||||
@@ -553,10 +552,10 @@ const getRoleById = function (rid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getModelValue = (model_id) => {
|
||||
const getModelName = (model_id) => {
|
||||
for (let i = 0; i < models.value.length; i++) {
|
||||
if (models.value[i].id === model_id) {
|
||||
return models.value[i].mValue
|
||||
return models.value[i].text
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<image-sd/>
|
||||
</van-tab>
|
||||
<van-tab title="DALL" name="dall">
|
||||
<van-empty description="功能正在开发中"/>
|
||||
<image-dall />
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
@@ -18,6 +18,7 @@
|
||||
import {ref} from "vue";
|
||||
import ImageMj from "@/views/mobile/pages/ImageMj.vue";
|
||||
import ImageSd from "@/views/mobile/pages/ImageSd.vue";
|
||||
import ImageDall from "@/views/mobile/pages/ImageDall.vue";
|
||||
|
||||
const activeName = ref("mj")
|
||||
</script>
|
||||
@@ -30,4 +31,4 @@ const activeName = ref("mj")
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
473
web/src/views/mobile/pages/ImageDall.vue
Normal file
473
web/src/views/mobile/pages/ImageDall.vue
Normal file
@@ -0,0 +1,473 @@
|
||||
<template>
|
||||
<div class="mobile-sd">
|
||||
<van-form @submit="generate">
|
||||
<van-cell-group inset>
|
||||
<div>
|
||||
<van-field
|
||||
v-model="quality"
|
||||
is-link
|
||||
label="图片质量"
|
||||
placeholder="选择图片质量"
|
||||
@click="showQualityPicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showQualityPicker" position="bottom" teleport="#app">
|
||||
<van-picker
|
||||
:columns="qualities"
|
||||
@cancel="showQualityPicker = false"
|
||||
@confirm="qualityConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<van-field
|
||||
v-model="size"
|
||||
is-link
|
||||
label="图片尺寸"
|
||||
placeholder="选择图片尺寸"
|
||||
@click="showSizePicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showSizePicker" position="bottom" teleport="#app">
|
||||
<van-picker
|
||||
:columns="sizes"
|
||||
@cancel="showSizePicker = false"
|
||||
@confirm="sizeConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<van-field
|
||||
v-model="style"
|
||||
is-link
|
||||
label="图片样式"
|
||||
placeholder="选择图片样式"
|
||||
@click="showStylePicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showStylePicker" position="bottom" teleport="#app">
|
||||
<van-picker
|
||||
:columns="styles"
|
||||
@cancel="showStylePicker = false"
|
||||
@confirm="styleConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<van-field
|
||||
v-model="params.prompt"
|
||||
rows="3"
|
||||
autosize
|
||||
type="textarea"
|
||||
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
|
||||
/>
|
||||
|
||||
<div class="text-line pt-6">
|
||||
<el-tag>绘图消耗{{ dallPower }}算力,当前算力:{{ power }}</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-button round block type="primary" native-type="submit">
|
||||
立即生成
|
||||
</van-button>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
|
||||
<h3>任务列表</h3>
|
||||
<div class="running-job-list">
|
||||
<van-empty v-if="runningJobs.length ===0"
|
||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
||||
image-size="80"
|
||||
description="暂无记录"
|
||||
/>
|
||||
<van-grid :gutter="10" :column-num="3" v-else>
|
||||
<van-grid-item v-for="item in runningJobs">
|
||||
<div v-if="item.progress > 0">
|
||||
<van-image :src="item['img_url']">
|
||||
<template v-slot:error>加载失败</template>
|
||||
</van-image>
|
||||
<div class="progress">
|
||||
<van-circle
|
||||
v-model:current-rate="item.progress"
|
||||
:rate="item.progress"
|
||||
:speed="100"
|
||||
:text="item.progress+'%'"
|
||||
:stroke-width="60"
|
||||
size="90px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="task-in-queue">
|
||||
<span class="icon"><i class="iconfont icon-quick-start"></i></span>
|
||||
<span class="text">排队中</span>
|
||||
</div>
|
||||
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
|
||||
<h3>创作记录</h3>
|
||||
<div class="finish-job-list">
|
||||
<van-empty v-if="finishedJobs.length ===0"
|
||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
||||
image-size="80"
|
||||
description="暂无记录"
|
||||
/>
|
||||
|
||||
<van-list v-else
|
||||
v-model:error="error"
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
error-text="请求失败,点击重新加载"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<van-grid :gutter="10" :column-num="2">
|
||||
<van-grid-item v-for="item in finishedJobs">
|
||||
<div class="job-item">
|
||||
<van-image
|
||||
:src="item['img_url']"
|
||||
:class="item['can_opt'] ? '' : 'upscale'"
|
||||
lazy-load
|
||||
@click="imageView(item)"
|
||||
fit="cover">
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20"/>
|
||||
</template>
|
||||
</van-image>
|
||||
|
||||
<div class="remove">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage($event, item)" circle/>
|
||||
<el-button type="warning" v-if="item.publish" @click="publishImage($event,item, false)"
|
||||
circle>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
<el-button type="success" v-else @click="publishImage($event, item, true)" circle>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
<el-button type="primary" @click="showPrompt(item)" circle>
|
||||
<i class="iconfont icon-prompt"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</van-list>
|
||||
|
||||
</div>
|
||||
<button style="display: none" class="copy-prompt-dall" :data-clipboard-text="prompt" id="copy-btn-dall">复制</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, onUnmounted, ref} from "vue"
|
||||
import {Delete} from "@element-plus/icons-vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Clipboard from "clipboard";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {
|
||||
showConfirmDialog,
|
||||
showDialog,
|
||||
showFailToast,
|
||||
showImagePreview,
|
||||
showNotify,
|
||||
showSuccessToast,
|
||||
showToast
|
||||
} from "vant";
|
||||
import {showLoginDialog} from "@/utils/libs";
|
||||
|
||||
const listBoxHeight = ref(window.innerHeight - 40)
|
||||
const mjBoxHeight = ref(window.innerHeight - 150)
|
||||
const item = ref({})
|
||||
const isLogin = ref(false)
|
||||
const activeColspan = ref([""])
|
||||
|
||||
window.onresize = () => {
|
||||
listBoxHeight.value = window.innerHeight - 40
|
||||
mjBoxHeight.value = window.innerHeight - 150
|
||||
}
|
||||
|
||||
const qualities = [
|
||||
{text: "标准", value: "standard"},
|
||||
{text: "高清", value: "hd"},
|
||||
]
|
||||
const sizes = [
|
||||
{text:"1024x1024",value:"1024x1024"},
|
||||
{text:"1792x1024",value:"1792x1024"},
|
||||
{text: "1024x1792",value:"1024x1792"},
|
||||
]
|
||||
const styles = [
|
||||
{text: "生动", value: "vivid"},
|
||||
{text: "自然", value: "natural"}
|
||||
]
|
||||
const params = ref({
|
||||
quality: qualities[0].value,
|
||||
size: sizes[0].value,
|
||||
style: styles[0].value,
|
||||
prompt: ""
|
||||
})
|
||||
const quality = ref(qualities[0].text)
|
||||
const size = ref(sizes[0].text)
|
||||
const style = ref(styles[0].text)
|
||||
|
||||
const showQualityPicker = ref(false)
|
||||
const showStylePicker = ref(false)
|
||||
const showSizePicker = ref(false)
|
||||
|
||||
|
||||
const runningJobs = ref([])
|
||||
const finishedJobs = ref([])
|
||||
const router = useRouter()
|
||||
const power = ref(0)
|
||||
const dallPower = ref(0) // 画一张 DALL 图片消耗算力
|
||||
|
||||
const socket = ref(null)
|
||||
const userId = ref(0)
|
||||
const heartbeatHandle = ref(null)
|
||||
const connect = () => {
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
if (host === '') {
|
||||
if (location.protocol === 'https:') {
|
||||
host = 'wss://' + location.host;
|
||||
} else {
|
||||
host = 'ws://' + location.host;
|
||||
}
|
||||
}
|
||||
|
||||
// 心跳函数
|
||||
const sendHeartbeat = () => {
|
||||
clearTimeout(heartbeatHandle.value)
|
||||
new Promise((resolve, reject) => {
|
||||
if (socket.value !== null) {
|
||||
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
|
||||
}
|
||||
resolve("success")
|
||||
}).then(() => {
|
||||
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
||||
});
|
||||
}
|
||||
|
||||
const _socket = new WebSocket(host + `/api/dall/client?user_id=${userId.value}`);
|
||||
_socket.addEventListener('open', () => {
|
||||
socket.value = _socket;
|
||||
|
||||
// 发送心跳消息
|
||||
sendHeartbeat()
|
||||
});
|
||||
|
||||
_socket.addEventListener('message', event => {
|
||||
if (event.data instanceof Blob) {
|
||||
fetchRunningJobs()
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchFinishJobs(page.value)
|
||||
}
|
||||
});
|
||||
|
||||
_socket.addEventListener('close', () => {
|
||||
if (socket.value !== null) {
|
||||
connect()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const clipboard = ref(null)
|
||||
const prompt = ref('')
|
||||
onMounted(() => {
|
||||
initData()
|
||||
clipboard.value = new Clipboard(".copy-prompt-dall");
|
||||
clipboard.value.on('success', () => {
|
||||
showNotify({type: 'success', message: '复制成功', duration: 1000})
|
||||
})
|
||||
clipboard.value.on('error', () => {
|
||||
showNotify({type: 'danger', message: '复制失败', duration: 2000})
|
||||
})
|
||||
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
dallPower.value = res.data.dall_power
|
||||
}).catch(e => {
|
||||
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
if (socket.value !== null) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const initData = () => {
|
||||
checkSession().then(user => {
|
||||
power.value = user['power']
|
||||
userId.value = user.id
|
||||
isLogin.value = true
|
||||
fetchRunningJobs()
|
||||
fetchFinishJobs(1)
|
||||
connect()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
});
|
||||
}
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/dall/jobs?status=0`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
if (jobs[i].progress === -1) {
|
||||
showNotify({
|
||||
message: `任务ID:${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
|
||||
type: 'danger',
|
||||
})
|
||||
power.value += dallPower.value
|
||||
continue
|
||||
}
|
||||
_jobs.push(jobs[i])
|
||||
}
|
||||
runningJobs.value = _jobs
|
||||
}).catch(e => {
|
||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
||||
})
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const error = ref(false)
|
||||
const page = ref(0)
|
||||
const pageSize = ref(10)
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
if (page === 1) {
|
||||
finishedJobs.value = res.data
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(res.data)
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
loading.value = false
|
||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
||||
})
|
||||
}
|
||||
|
||||
const onLoad = () => {
|
||||
page.value += 1
|
||||
fetchFinishJobs(page.value)
|
||||
}
|
||||
|
||||
// 创建绘图任务
|
||||
const promptRef = ref(null)
|
||||
const generate = () => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
|
||||
if (params.value.prompt === '') {
|
||||
promptRef.value.focus()
|
||||
return showToast("请输入绘画提示词!")
|
||||
}
|
||||
|
||||
if (!params.value.seed) {
|
||||
params.value.seed = -1
|
||||
}
|
||||
params.value.session_id = getSessionId()
|
||||
httpPost("/api/dall/image", params.value).then(() => {
|
||||
showSuccessToast("绘画任务推送成功,请耐心等待任务执行...")
|
||||
power.value -= dallPower.value
|
||||
}).catch(e => {
|
||||
showFailToast("任务推送失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const showPrompt = (item) => {
|
||||
prompt.value = item.prompt
|
||||
showConfirmDialog({
|
||||
title: "绘画提示词",
|
||||
message: item.prompt,
|
||||
confirmButtonText: "复制",
|
||||
cancelButtonText: "关闭",
|
||||
}).then(() => {
|
||||
document.querySelector('#copy-btn-dall').click()
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const removeImage = (event, item) => {
|
||||
event.stopPropagation()
|
||||
showConfirmDialog({
|
||||
title: '标题',
|
||||
message:
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
}).then(() => {
|
||||
httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
showSuccessToast("任务删除成功")
|
||||
}).catch(e => {
|
||||
showFailToast("任务删除失败:" + e.message)
|
||||
})
|
||||
}).catch(() => {
|
||||
showToast("您取消了操作")
|
||||
});
|
||||
}
|
||||
|
||||
// 发布图片到作品墙
|
||||
const publishImage = (event, item, action) => {
|
||||
event.stopPropagation()
|
||||
let text = "图片发布"
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
|
||||
showSuccessToast(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
showFailToast(text + "失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const imageView = (item) => {
|
||||
showImagePreview([item['img_url']]);
|
||||
}
|
||||
|
||||
|
||||
const qualityConfirm = (item) => {
|
||||
params.value.quality = item.selectedOptions[0].value;
|
||||
quality.value = item.selectedOptions[0].text
|
||||
showQualityPicker.value = false
|
||||
}
|
||||
|
||||
const styleConfirm = (item) => {
|
||||
params.value.style = item.selectedOptions[0].value;
|
||||
style.value = item.selectedOptions[0].text
|
||||
showStylePicker.value = false
|
||||
}
|
||||
|
||||
const sizeConfirm =(item) => {
|
||||
params.value.size = item.selectedOptions[0].value
|
||||
size.value=item.selectedOptions[0].text
|
||||
showSizePicker.value =false
|
||||
}
|
||||
|
||||
const showInfo = (message) => {
|
||||
showDialog({
|
||||
title: "参数说明",
|
||||
message: message,
|
||||
}).then(() => {
|
||||
// on close
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@import "@/assets/css/mobile/image-sd.styl"
|
||||
</style>
|
||||
@@ -43,8 +43,25 @@
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-tab>
|
||||
<van-tab title="DALL" name="dalle3">
|
||||
<van-empty description="功能正在开发中"/>
|
||||
<van-tab title="DALL" name="dall">
|
||||
<van-list
|
||||
v-model:error="data['dall'].error"
|
||||
v-model:loading="data['dall'].loading"
|
||||
:finished="data['dall'].finished"
|
||||
error-text="请求失败,点击重新加载"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<van-cell v-for="item in data['dall'].data" :key="item.id">
|
||||
<van-image :src="item['img_thumb']" @click="imageView(item)" fit="cover"/>
|
||||
|
||||
<div class="opt-box">
|
||||
<el-button type="primary" @click="showPrompt(item)" circle>
|
||||
<i class="iconfont icon-prompt"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
@@ -81,13 +98,13 @@ const data = ref({
|
||||
url: "/api/sd/imgWall",
|
||||
data: []
|
||||
},
|
||||
"dalle3": {
|
||||
"dall": {
|
||||
loading: false,
|
||||
finished: false,
|
||||
error: false,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
url: "/api/dalle3/imgWall",
|
||||
url: "/api/dall/imgWall",
|
||||
data: []
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user