mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-08 18:23:45 +08:00
merge conflicts for v4.0.5
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.3
|
||||
VUE_APP_VERSION=v4.0.5
|
||||
|
||||
@@ -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.3
|
||||
VUE_APP_VERSION=v4.0.5
|
||||
|
||||
1884
web/package-lock.json
generated
1884
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@
|
||||
"markmap-lib": "^0.16.1",
|
||||
"markmap-view": "^0.16.0",
|
||||
"md-editor-v3": "^2.2.1",
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.1.4",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.1",
|
||||
|
||||
@@ -32,7 +32,6 @@ window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
|
||||
|
||||
<style lang="stylus">
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -67,4 +66,14 @@ html, body {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.van-toast--fail {
|
||||
background #fef0f0
|
||||
color #f56c6c
|
||||
}
|
||||
|
||||
.van-toast--success {
|
||||
background #D6FBCC
|
||||
color #07C160
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,30 +1,95 @@
|
||||
.admin-home {
|
||||
.header {
|
||||
|
||||
}
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
--el-color-primary: #409eff;
|
||||
--el-color-primary-light-3: #3375b9;
|
||||
--el-color-primary-light-5: #2a598a;
|
||||
--el-color-primary-light-7: #213d5b;
|
||||
--el-color-primary-light-8: #1d3043;
|
||||
--el-color-primary-light-9: #18222c;
|
||||
--el-color-primary-dark-2: #66b1ff;
|
||||
--el-color-success: #67c23a;
|
||||
--el-color-success-light-3: #4e8e2f;
|
||||
--el-color-success-light-5: #3e6b27;
|
||||
--el-color-success-light-7: #2d481f;
|
||||
--el-color-success-light-8: #25371c;
|
||||
--el-color-success-light-9: #1c2518;
|
||||
--el-color-success-dark-2: #85ce61;
|
||||
--el-color-warning: #e6a23c;
|
||||
--el-color-warning-light-3: #a77730;
|
||||
--el-color-warning-light-5: #7d5b28;
|
||||
--el-color-warning-light-7: #533f20;
|
||||
--el-color-warning-light-8: #3e301c;
|
||||
--el-color-warning-light-9: #292218;
|
||||
--el-color-warning-dark-2: #ebb563;
|
||||
--el-color-danger: #f56c6c;
|
||||
--el-color-danger-light-3: #b25252;
|
||||
--el-color-danger-light-5: #854040;
|
||||
--el-color-danger-light-7: #582e2e;
|
||||
--el-color-danger-light-8: #412626;
|
||||
--el-color-danger-light-9: #2b1d1d;
|
||||
--el-color-danger-dark-2: #f78989;
|
||||
--el-color-error: #f56c6c;
|
||||
--el-color-error-light-3: #b25252;
|
||||
--el-color-error-light-5: #854040;
|
||||
--el-color-error-light-7: #582e2e;
|
||||
--el-color-error-light-8: #412626;
|
||||
--el-color-error-light-9: #2b1d1d;
|
||||
--el-color-error-dark-2: #f78989;
|
||||
--el-color-info: #909399;
|
||||
--el-color-info-light-3: #6b6d71;
|
||||
--el-color-info-light-5: #525457;
|
||||
--el-color-info-light-7: #393a3c;
|
||||
--el-color-info-light-8: #2d2d2f;
|
||||
--el-color-info-light-9: #202121;
|
||||
--el-color-info-dark-2: #a6a9ad;
|
||||
--el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.36), 0px 8px 20px rgba(0, 0, 0, 0.72);
|
||||
--el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.72);
|
||||
--el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.72);
|
||||
--el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.72), 0px 12px 32px #000000, 0px 8px 16px -8px #000000;
|
||||
--el-bg-color-page: #0a0a0a;
|
||||
--el-bg-color: #141414;
|
||||
--el-bg-color-overlay: #1d1e1f;
|
||||
--el-text-color-primary: #E5EAF3;
|
||||
--el-text-color-regular: #CFD3DC;
|
||||
--el-text-color-secondary: #A3A6AD;
|
||||
--el-text-color-placeholder: #8D9095;
|
||||
--el-text-color-disabled: #6C6E72;
|
||||
--el-border-color-darker: #636466;
|
||||
--el-border-color-dark: #58585B;
|
||||
--el-border-color: #4C4D4F;
|
||||
--el-border-color-light: #414243;
|
||||
--el-border-color-lighter: #363637;
|
||||
--el-border-color-extra-light: #2B2B2C;
|
||||
--el-fill-color-darker: #424243;
|
||||
--el-fill-color-dark: #39393A;
|
||||
--el-fill-color: #303030;
|
||||
--el-fill-color-light: #262727;
|
||||
--el-fill-color-lighter: #1D1D1D;
|
||||
--el-fill-color-extra-light: #191919;
|
||||
--el-fill-color-blank: transparent;
|
||||
--el-mask-color: rgba(0, 0, 0, 0.8);
|
||||
--el-mask-color-extra-light: rgba(0, 0, 0, 0.3)
|
||||
--el-menu-bg-color-dark: #39393A
|
||||
--el-menu-bg-color-darker: #424243
|
||||
}
|
||||
|
||||
.login-wrap {
|
||||
background: #324157;
|
||||
}
|
||||
.dark .el-button {
|
||||
--el-button-disabled-text-color: rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
|
||||
.plugins-tips {
|
||||
background: #eef1f6;
|
||||
}
|
||||
.dark .el-card {
|
||||
--el-card-bg-color: var(--el-bg-color-overlay)
|
||||
}
|
||||
|
||||
.plugins-tips a {
|
||||
color: #20a0ff;
|
||||
}
|
||||
|
||||
.tags-li.active {
|
||||
border: 1px solid #409EFF;
|
||||
background-color: #409EFF;
|
||||
}
|
||||
|
||||
.message-title {
|
||||
color: #20a0ff;
|
||||
}
|
||||
|
||||
.collapse-btn:hover {
|
||||
background: rgb(40, 52, 70);
|
||||
}
|
||||
.dark .el-empty {
|
||||
--el-empty-fill-color-0: var(--el-color-black);
|
||||
--el-empty-fill-color-1: #4b4b52;
|
||||
--el-empty-fill-color-2: #36383d;
|
||||
--el-empty-fill-color-3: #1e1e20;
|
||||
--el-empty-fill-color-4: #262629;
|
||||
--el-empty-fill-color-5: #202124;
|
||||
--el-empty-fill-color-6: #212224;
|
||||
--el-empty-fill-color-7: #1b1c1f;
|
||||
--el-empty-fill-color-8: #1c1d1f;
|
||||
--el-empty-fill-color-9: #18181a
|
||||
}
|
||||
@@ -37,22 +37,28 @@ body {
|
||||
transition: left .3s ease-in-out;
|
||||
background: #f0f0f0;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px; /* 滚动条宽度 */
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #666666;
|
||||
border-radius 8px
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #999999;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: auto;
|
||||
height: 100vh;
|
||||
overflow-y: scroll;
|
||||
box-sizing: border-box;
|
||||
|
||||
.container {
|
||||
padding: 15px 20px 30px 20px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background: #ffffff;
|
||||
margin-bottom 80px
|
||||
|
||||
.handle-box {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.crumbs {
|
||||
@@ -60,7 +66,7 @@ body {
|
||||
}
|
||||
|
||||
.el-table th {
|
||||
background-color: #f5f7fa !important;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
@@ -142,6 +148,23 @@ body {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
background: var(--el-bg-color);
|
||||
|
||||
.container {
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.crumbs {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.el-table th {
|
||||
background-color: var(--el-fill-color-darker);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.content-collapse {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
.mobile-chat-list {
|
||||
.content {
|
||||
padding-top 46px
|
||||
padding-bottom 60px
|
||||
|
||||
.van-list {
|
||||
.van-cell__value {
|
||||
.chat-list-item {
|
||||
@@ -31,19 +34,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
.van-popup {
|
||||
.picker-option {
|
||||
display flex
|
||||
width 100%
|
||||
padding 0 10px
|
||||
overflow hidden
|
||||
height 20px
|
||||
text-overflow ellipsis
|
||||
|
||||
.van-image {
|
||||
width 20px;
|
||||
height 20px;
|
||||
margin-right 5px
|
||||
}
|
||||
}
|
||||
}
|
||||
@import "model-select.styl"
|
||||
@@ -1,16 +1,29 @@
|
||||
.mobile-chat {
|
||||
.message-list-box {
|
||||
//padding-top 50px
|
||||
padding-bottom 10px
|
||||
overflow-x auto
|
||||
background #F5F5F5;
|
||||
.van-nav-bar {
|
||||
position static
|
||||
|
||||
.van-cell {
|
||||
background none
|
||||
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
.setting {
|
||||
font-size 18px
|
||||
}
|
||||
}
|
||||
|
||||
.chat-list-wrapper {
|
||||
padding 10px 0 10px 0
|
||||
background var(--van-background);
|
||||
overflow hidden
|
||||
|
||||
|
||||
.message-list-box {
|
||||
overflow auto
|
||||
|
||||
.van-cell {
|
||||
background none
|
||||
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.chat-box-wrapper {
|
||||
.van-sticky {
|
||||
.van-cell-group--inset {
|
||||
@@ -68,8 +81,10 @@
|
||||
|
||||
.van-theme-dark {
|
||||
.mobile-chat {
|
||||
.message-list-box {
|
||||
.chat-list-wrapper {
|
||||
background #232425;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import "model-select.styl"
|
||||
@@ -8,7 +8,7 @@
|
||||
.rate {
|
||||
display: flex;
|
||||
justify-content center
|
||||
background-color #f5f5f5
|
||||
background-color var(--van-background-3)
|
||||
padding 5px 10px
|
||||
margin 5px 0
|
||||
border-radius 5px
|
||||
@@ -24,14 +24,14 @@
|
||||
|
||||
.text {
|
||||
text-align center
|
||||
color #555555
|
||||
color var(--van-text-color)
|
||||
}
|
||||
}
|
||||
|
||||
.model {
|
||||
display: flex;
|
||||
justify-content center
|
||||
background-color #f5f5f5
|
||||
background-color var(--van-background-3)
|
||||
padding 6px
|
||||
margin 5px 0
|
||||
border-radius 5px
|
||||
@@ -48,12 +48,12 @@
|
||||
|
||||
.text {
|
||||
text-align center
|
||||
color #555555
|
||||
color var(--van-text-color)
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color #e5e5e5
|
||||
background-color var(--van-text-color-3)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,11 @@
|
||||
}
|
||||
|
||||
|
||||
color var(--van-text-color)
|
||||
.pt-6 {
|
||||
padding 15px 10px
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
padding 10px
|
||||
line-height 1.5
|
||||
|
||||
@@ -80,8 +80,10 @@
|
||||
}
|
||||
|
||||
|
||||
color var(--van-text-color)
|
||||
|
||||
.pt-6 {
|
||||
padding 10px
|
||||
padding 15px 10px
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
|
||||
16
web/src/assets/css/mobile/model-select.styl
Normal file
16
web/src/assets/css/mobile/model-select.styl
Normal file
@@ -0,0 +1,16 @@
|
||||
.van-popup {
|
||||
.picker-option {
|
||||
display flex
|
||||
width 100%
|
||||
padding 0 10px
|
||||
overflow hidden
|
||||
height 20px
|
||||
text-overflow ellipsis
|
||||
|
||||
.van-image {
|
||||
width 20px;
|
||||
height 20px;
|
||||
margin-right 5px
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
web/src/assets/img/ai-bg-02.png
Normal file
BIN
web/src/assets/img/ai-bg-02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
BIN
web/src/assets/img/ai-bg.jpg
Normal file
BIN
web/src/assets/img/ai-bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 388 KiB |
@@ -57,6 +57,7 @@ export default defineComponent({
|
||||
if (!this.finalTokens) {
|
||||
httpPost("/api/chat/tokens", {text: this.content, model: this.model}).then(res => {
|
||||
this.finalTokens = res.data;
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,8 @@
|
||||
|
||||
<script setup>
|
||||
import {computed, onMounted, ref} from "vue"
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {Plus} from "@element-plus/icons-vue";
|
||||
import Compressor from "compressorjs";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Iphone/>
|
||||
<Message/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
@@ -9,17 +9,21 @@
|
||||
>
|
||||
<div class="form">
|
||||
|
||||
<el-form :model="form" label-width="120px" label-position="left">
|
||||
<el-form :model="form" label-width="80px" label-position="left">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form.username" placeholder="手机号/邮箱地址"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码">
|
||||
<div class="code-box">
|
||||
<el-input v-model="form.code" maxlength="6"/>
|
||||
<send-msg size="" :receiver="form.username" style="margin-left: 10px; min-width: 100px"/>
|
||||
</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-input v-model="form.code" maxlength="6"/>
|
||||
<el-col :span="12">
|
||||
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<send-msg size="" :receiver="form.username"/>
|
||||
<el-col :span="12" style="justify-content: right">
|
||||
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
@@ -97,9 +101,13 @@ const close = function () {
|
||||
<style lang="stylus">
|
||||
.reset-pass {
|
||||
.form {
|
||||
padding 10px 40px
|
||||
padding 10px 20px
|
||||
}
|
||||
.code-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
text-align center
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
style="width: 360px;"
|
||||
>
|
||||
<slide-captcha
|
||||
v-if="isIphone()"
|
||||
v-if="isMobile()"
|
||||
:bg-img="bgImg"
|
||||
:bk-img="bkImg"
|
||||
:result="result"
|
||||
@@ -38,12 +38,13 @@
|
||||
import {ref} from "vue";
|
||||
import lodash from 'lodash'
|
||||
import {validateEmail, validateMobile} from "@/utils/validate";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import CaptchaPlus from "@/components/CaptchaPlus.vue";
|
||||
import SlideCaptcha from "@/components/SlideCaptcha.vue";
|
||||
import {isIphone} from "@/utils/libs";
|
||||
import {isMobile} from "@/utils/libs";
|
||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
receiver: String,
|
||||
size: String,
|
||||
@@ -65,13 +66,13 @@ const handleRequestCaptCode = () => {
|
||||
thumbBase64.value = data.thumb
|
||||
captKey.value = data.key
|
||||
}).catch(e => {
|
||||
ElMessage.error('获取人机验证数据失败:' + e.message)
|
||||
showMessageError('获取人机验证数据失败:' + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const handleConfirm = (dots) => {
|
||||
if (lodash.size(dots) <= 0) {
|
||||
return ElMessage.error('请进行人机验证再操作')
|
||||
return showMessageError('请进行人机验证再操作')
|
||||
}
|
||||
|
||||
let dotArr = []
|
||||
@@ -87,19 +88,19 @@ const handleConfirm = (dots) => {
|
||||
showCaptcha.value = false
|
||||
sendMsg()
|
||||
}).catch(() => {
|
||||
ElMessage.error('人机验证失败')
|
||||
showMessageError('人机验证失败')
|
||||
handleRequestCaptCode()
|
||||
})
|
||||
}
|
||||
|
||||
const loadCaptcha = () => {
|
||||
if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) {
|
||||
return ElMessage.error("请输入合法的手机号/邮箱地址")
|
||||
return showMessageError("请输入合法的手机号/邮箱地址")
|
||||
}
|
||||
|
||||
showCaptcha.value = true
|
||||
// iphone 手机用滑动验证码
|
||||
if (isIphone()) {
|
||||
// 手机用滑动验证码
|
||||
if (isMobile()) {
|
||||
getSlideCaptcha()
|
||||
} else {
|
||||
handleRequestCaptCode()
|
||||
@@ -113,7 +114,7 @@ const sendMsg = () => {
|
||||
|
||||
canSend.value = false
|
||||
httpPost('/api/sms/code', {receiver: props.receiver, key: captKey.value, dots: dots.value}).then(() => {
|
||||
ElMessage.success('验证码发送成功')
|
||||
showMessageOK('验证码发送成功')
|
||||
let time = 120
|
||||
btnText.value = time
|
||||
const handler = setInterval(() => {
|
||||
@@ -128,7 +129,7 @@ const sendMsg = () => {
|
||||
}, 1000)
|
||||
}).catch(e => {
|
||||
canSend.value = true
|
||||
ElMessage.error('验证码发送失败:' + e.message)
|
||||
showMessageError('验证码发送失败:' + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ const getSlideCaptcha = () => {
|
||||
bgImg.value = res.data.bgImg
|
||||
captKey.value = res.data.key
|
||||
}).catch(e => {
|
||||
ElMessage.error('获取人机验证数据失败:' + e.message)
|
||||
showMessageError('获取人机验证数据失败:' + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="header admin-header">
|
||||
<div :class="'admin-header '+theme">
|
||||
<!-- 折叠按钮 -->
|
||||
<div class="collapse-btn" @click="collapseChange">
|
||||
<el-icon v-if="sidebar.collapse">
|
||||
@@ -17,17 +17,15 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="header-user-con">
|
||||
<!-- 消息中心 -->
|
||||
<div class="btn-bell">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="message ? `有${message}条未读消息` : `消息中心`"
|
||||
placement="bottom"
|
||||
>
|
||||
<i class="iconfont icon-bell"></i>
|
||||
</el-tooltip>
|
||||
<span class="btn-bell-badge" v-if="message"></span>
|
||||
</div>
|
||||
<!-- 切换主题 -->
|
||||
<el-switch
|
||||
style="margin-right: 10px"
|
||||
v-model="dark"
|
||||
inline-prompt
|
||||
:active-action-icon="Moon"
|
||||
:inactive-action-icon="Sunny"
|
||||
@change="changeTheme"
|
||||
/>
|
||||
<!-- 用户名下拉菜单 -->
|
||||
<el-dropdown class="user-name" :hide-on-click="true" trigger="click">
|
||||
<span class="el-dropdown-link">
|
||||
@@ -38,20 +36,9 @@
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
|
||||
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
|
||||
<el-dropdown-item>
|
||||
<i class="iconfont icon-github"></i>
|
||||
<span>{{ sysTitle }}</span>
|
||||
</el-dropdown-item>
|
||||
</a>
|
||||
<el-dropdown-item>
|
||||
<i class="iconfont icon-version"></i> 当前版本:{{ version }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="showDialog = true">
|
||||
<i class="iconfont icon-reward"></i>
|
||||
<span>打赏作者</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided @click="logout">
|
||||
<i class="iconfont icon-logout"></i>
|
||||
<span>退出登录</span>
|
||||
@@ -62,41 +49,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="showDialog"
|
||||
:show-close="true"
|
||||
class="donate-dialog"
|
||||
width="400px"
|
||||
title="请作者喝杯咖啡"
|
||||
>
|
||||
<el-alert type="info" :closable="false">
|
||||
<p>如果你觉得这个项目对你有帮助,并且情况允许的话,可以请作者喝杯咖啡,非常感谢你的支持~</p>
|
||||
</el-alert>
|
||||
<p>
|
||||
<el-image :src="donateImg"/>
|
||||
</p>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {onMounted, ref} from 'vue';
|
||||
import {computed, onMounted, ref} from 'vue';
|
||||
import {getMenuItems, useSidebarStore} from '@/store/sidebar';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {ArrowDown, ArrowRight, Expand, Fold} from "@element-plus/icons-vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {ArrowDown, ArrowRight, Expand, Fold, Moon, Sunny} from "@element-plus/icons-vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {removeAdminToken} from "@/store/session";
|
||||
|
||||
const message = ref(5);
|
||||
const sysTitle = ref(process.env.VUE_APP_TITLE)
|
||||
const version = ref(process.env.VUE_APP_VERSION)
|
||||
const avatar = ref('/images/user-info.jpg')
|
||||
const donateImg = ref('/images/wechat-pay.png')
|
||||
const showDialog = ref(false)
|
||||
const sidebar = useSidebarStore();
|
||||
const router = useRouter();
|
||||
const breadcrumb = ref([])
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
theme: String,
|
||||
});
|
||||
|
||||
const theme = computed(() => {
|
||||
return props.theme
|
||||
})
|
||||
const dark = ref(props.theme === 'dark' ? true : false)
|
||||
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['changeTheme']);
|
||||
const changeTheme = () => {
|
||||
emits('changeTheme', dark.value)
|
||||
}
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
initBreadCrumb(to.path)
|
||||
@@ -166,7 +151,7 @@ const logout = function () {
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="stylus">
|
||||
.header {
|
||||
.admin-header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow hidden
|
||||
@@ -174,7 +159,6 @@ const logout = function () {
|
||||
font-size: 22px;
|
||||
color: #303133;
|
||||
background-color #ffffff
|
||||
border-bottom 1px solid #eaecef
|
||||
|
||||
.collapse-btn {
|
||||
display: flex;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="sidebar">
|
||||
<div class="logo">
|
||||
<div :class="'sidebar '+theme">
|
||||
<a class="logo" href="/" target="_blank">
|
||||
<el-image :src="logo"/>
|
||||
<span class="text" v-show="!sidebar.collapse">{{ title }}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<el-menu
|
||||
class="sidebar-el-menu"
|
||||
@@ -54,9 +54,9 @@
|
||||
<script setup>
|
||||
import {computed, ref} from 'vue';
|
||||
import {setMenuItems, useSidebarStore} from '@/store/sidebar';
|
||||
import {useRoute} from 'vue-router';
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useRoute} from "vue-router";
|
||||
|
||||
const title = ref('Chat-Plus-Admin')
|
||||
const logo = ref('/images/logo.png')
|
||||
@@ -69,6 +69,15 @@ httpGet('/api/admin/config/get?key=system').then(res => {
|
||||
ElMessage.error("加载系统配置失败: " + e.message)
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
theme: String,
|
||||
});
|
||||
|
||||
const theme = computed(() => {
|
||||
return props.theme
|
||||
})
|
||||
|
||||
const items = [
|
||||
{
|
||||
icon: 'home',
|
||||
@@ -190,6 +199,7 @@ setMenuItems(items)
|
||||
width 219px
|
||||
background-color #324157
|
||||
padding 6px 15px;
|
||||
cursor pointer
|
||||
|
||||
.el-image {
|
||||
width 36px;
|
||||
@@ -234,4 +244,37 @@ setMenuItems(items)
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.sidebar.dark {
|
||||
border-right 1px solid var(--el-border-color-dark)
|
||||
|
||||
.logo {
|
||||
background var(--el-bg-color)
|
||||
border-right 1px solid var(--el-border-color)
|
||||
|
||||
.text {
|
||||
color var(--el-text-color-regular)
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
background var(--el-bg-color)
|
||||
|
||||
.el-menu-item.is-active {
|
||||
background-color var(--el-menu-bg-color-dark)
|
||||
}
|
||||
|
||||
.el-menu-item:hover {
|
||||
background-color var(--el-menu-bg-color-darker)
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-el-menu:not(.el-menu--collapse) {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-color var(--el-border-color)
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="tags" v-if="tags.show">
|
||||
<div :class="'tags '+theme" v-if="tags.show">
|
||||
<ul>
|
||||
<li
|
||||
class="tags-li"
|
||||
@@ -38,6 +38,16 @@ import {onBeforeRouteUpdate, useRoute, useRouter} from 'vue-router';
|
||||
import {ArrowDown, Close} from "@element-plus/icons-vue";
|
||||
import {checkAdminSession} from "@/action/session";
|
||||
import {ElMessageBox} from "element-plus";
|
||||
import {computed} from "vue";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
theme: String,
|
||||
});
|
||||
|
||||
const theme = computed(() => {
|
||||
return props.theme
|
||||
})
|
||||
|
||||
const router = useRouter();
|
||||
checkAdminSession().catch(() => {
|
||||
@@ -108,74 +118,75 @@ const handleTags = (command) => {
|
||||
// });
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped lang="stylus">
|
||||
.tags {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
padding-right: 120px;
|
||||
padding 5px 120px 5px 10px
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
|
||||
|
||||
ul {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.tags-li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
float: left;
|
||||
margin: 3px 5px 2px 3px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
height: 23px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
background: var(--el-bg-color);
|
||||
padding: 0 5px 0 12px;
|
||||
color: var(--el-text-color);
|
||||
-webkit-transition: all 0.3s ease-in;
|
||||
-moz-transition: all 0.3s ease-in;
|
||||
transition: all 0.3s ease-in;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-menu-bg-color-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.tags-li-title {
|
||||
float: left;
|
||||
max-width: 80px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tags-li.active .tags-li-title {
|
||||
color: var(--el-color-primary)
|
||||
}
|
||||
}
|
||||
|
||||
.tags-close-box {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 7px;
|
||||
box-sizing: border-box;
|
||||
padding-top: 1px;
|
||||
text-align: center;
|
||||
width: 110px;
|
||||
height: 30px;
|
||||
background: var(--el-bg-color);
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.tags ul {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.tags.dark {
|
||||
border-bottom 1px solid var(--el-border-color)
|
||||
}
|
||||
|
||||
.tags-li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
float: left;
|
||||
margin: 3px 5px 2px 3px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
height: 23px;
|
||||
border: 1px solid #e9eaec;
|
||||
background: #fff;
|
||||
padding: 0 5px 0 12px;
|
||||
color: #666;
|
||||
-webkit-transition: all 0.3s ease-in;
|
||||
-moz-transition: all 0.3s ease-in;
|
||||
transition: all 0.3s ease-in;
|
||||
}
|
||||
|
||||
.tags-li:not(.active):hover {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.tags-li.active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tags-li-title {
|
||||
float: left;
|
||||
max-width: 80px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tags-li.active .tags-li-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tags-close-box {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 2px;
|
||||
box-sizing: border-box;
|
||||
padding-top: 1px;
|
||||
text-align: center;
|
||||
width: 110px;
|
||||
height: 30px;
|
||||
background: #fff;
|
||||
//box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1); z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {onMounted, ref} from "vue";
|
||||
import Clipboard from "clipboard";
|
||||
import {showNotify} from "vant";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars,no-undef
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||
// * Use of this source code is governed by a Apache-2.0 license
|
||||
// * that can be found in the LICENSE file.
|
||||
// * @Author yangjian102621@163.com
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import {createApp} from 'vue'
|
||||
import ElementPlus from "element-plus"
|
||||
import "element-plus/dist/index.css"
|
||||
@@ -5,6 +12,7 @@ import 'vant/lib/index.css';
|
||||
import App from './App.vue'
|
||||
import {createPinia} from "pinia";
|
||||
import {
|
||||
ActionSheet,
|
||||
Badge,
|
||||
Button,
|
||||
Cell,
|
||||
@@ -15,6 +23,7 @@ import {
|
||||
CollapseItem,
|
||||
ConfigProvider,
|
||||
Dialog,
|
||||
Divider,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
Empty,
|
||||
@@ -29,6 +38,7 @@ import {
|
||||
List,
|
||||
Loading,
|
||||
NavBar,
|
||||
NoticeBar,
|
||||
Notify,
|
||||
Overlay,
|
||||
Picker,
|
||||
@@ -52,8 +62,8 @@ import {router} from "@/router";
|
||||
import 'v3-waterfall/dist/style.css'
|
||||
import V3waterfall from "v3-waterfall";
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(createPinia())
|
||||
const app = createApp(App);
|
||||
app.use(createPinia());
|
||||
app.use(ConfigProvider);
|
||||
app.use(Tabbar);
|
||||
app.use(TabbarItem);
|
||||
@@ -97,6 +107,9 @@ app.use(Lazyload);
|
||||
app.use(ImagePreview);
|
||||
app.use(Tab);
|
||||
app.use(Tabs);
|
||||
app.use(Divider);
|
||||
app.use(NoticeBar);
|
||||
app.use(ActionSheet);
|
||||
app.use(router).use(ElementPlus).mount('#app')
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||
// * Use of this source code is governed by a Apache-2.0 license
|
||||
// * that can be found in the LICENSE file.
|
||||
// * @Author yangjian102621@163.com
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import {createRouter, createWebHistory} from "vue-router";
|
||||
|
||||
const routes = [
|
||||
@@ -201,8 +208,13 @@ const routes = [
|
||||
path: '/mobile',
|
||||
meta: {title: 'Geek-AI v4.0'},
|
||||
component: () => import('@/views/mobile/Home.vue'),
|
||||
redirect: '/mobile/chat',
|
||||
redirect: '/mobile/index',
|
||||
children: [
|
||||
{
|
||||
path: '/mobile/index',
|
||||
name: 'mobile-index',
|
||||
component: () => import('@/views/mobile/Index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/mobile/chat',
|
||||
name: 'mobile-chat',
|
||||
@@ -219,22 +231,22 @@ const routes = [
|
||||
component: () => import('@/views/mobile/Profile.vue'),
|
||||
},
|
||||
{
|
||||
path: '/mobile/img-wall',
|
||||
path: '/mobile/imgWall',
|
||||
name: 'mobile-img-wall',
|
||||
component: () => import('@/views/mobile/ImgWall.vue'),
|
||||
component: () => import('@/views/mobile/pages/ImgWall.vue'),
|
||||
},
|
||||
{
|
||||
path: '/mobile/chat/session',
|
||||
name: 'mobile-chat-session',
|
||||
component: () => import('@/views/mobile/ChatSession.vue'),
|
||||
},
|
||||
{
|
||||
path: '/mobile/chat/export',
|
||||
name: 'mobile-chat-export',
|
||||
component: () => import('@/views/mobile/ChatExport.vue'),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/mobile/chat/session',
|
||||
name: 'mobile-chat-session',
|
||||
component: () => import('@/views/mobile/ChatSession.vue'),
|
||||
},
|
||||
{
|
||||
path: '/mobile/chat/export',
|
||||
name: 'mobile-chat-export',
|
||||
component: () => import('@/views/mobile/ChatExport.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'test',
|
||||
|
||||
6
web/src/store/eventbus.js
Normal file
6
web/src/store/eventbus.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// 导入mitt包
|
||||
import mitt from 'mitt'
|
||||
// 创建EventBus实例对象
|
||||
const bus = mitt()
|
||||
// 共享出eventbus的实例对象
|
||||
export default bus
|
||||
@@ -5,25 +5,11 @@ import Storage from "good-storage";
|
||||
* storage handler
|
||||
*/
|
||||
|
||||
const SessionIDKey = process.env.VUE_APP_KEY_PREFIX + 'SESSION_ID';
|
||||
const UserTokenKey = process.env.VUE_APP_KEY_PREFIX + "Authorization";
|
||||
const AdminTokenKey = process.env.VUE_APP_KEY_PREFIX + "Admin-Authorization"
|
||||
|
||||
export function getSessionId() {
|
||||
let sessionId = Storage.get(SessionIDKey)
|
||||
if (!sessionId) {
|
||||
sessionId = randString(42)
|
||||
setSessionId(sessionId)
|
||||
}
|
||||
return sessionId
|
||||
}
|
||||
|
||||
export function removeSessionId() {
|
||||
Storage.remove(SessionIDKey)
|
||||
}
|
||||
|
||||
export function setSessionId(sessionId) {
|
||||
Storage.set(SessionIDKey, sessionId)
|
||||
return randString(42)
|
||||
}
|
||||
|
||||
export function getUserToken() {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||
// * Use of this source code is governed by a Apache-2.0 license
|
||||
// * that can be found in the LICENSE file.
|
||||
// * @Author yangjian102621@163.com
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import Storage from "good-storage";
|
||||
|
||||
const MOBILE_THEME = process.env.VUE_APP_KEY_PREFIX + "MOBILE_THEME"
|
||||
const ADMIN_THEME = process.env.VUE_APP_KEY_PREFIX + "ADMIN_THEME"
|
||||
|
||||
export function getMobileTheme() {
|
||||
return Storage.get(MOBILE_THEME) ? Storage.get(MOBILE_THEME) : 'light'
|
||||
@@ -8,4 +16,12 @@ export function getMobileTheme() {
|
||||
|
||||
export function setMobileTheme(theme) {
|
||||
Storage.set(MOBILE_THEME, theme)
|
||||
}
|
||||
|
||||
export function getAdminTheme() {
|
||||
return Storage.get(ADMIN_THEME) ? Storage.get(ADMIN_THEME) : 'light'
|
||||
}
|
||||
|
||||
export function setAdminTheme(theme) {
|
||||
Storage.set(ADMIN_THEME, theme)
|
||||
}
|
||||
43
web/src/utils/dialog.js
Normal file
43
web/src/utils/dialog.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Util lib functions
|
||||
*/
|
||||
import {showConfirmDialog, showFailToast, showSuccessToast, showToast} from "vant";
|
||||
import {isMobile} from "@/utils/libs";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
export function showLoginDialog(router) {
|
||||
showConfirmDialog({
|
||||
title: '登录',
|
||||
message:
|
||||
'此操作需要登录才能进行,前往登录?',
|
||||
}).then(() => {
|
||||
router.push("/login")
|
||||
}).catch(() => {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
|
||||
export function showMessageOK(message) {
|
||||
if (isMobile()) {
|
||||
showSuccessToast(message)
|
||||
} else {
|
||||
ElMessage.success(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function showMessageInfo(message) {
|
||||
if (isMobile()) {
|
||||
showToast(message)
|
||||
} else {
|
||||
ElMessage.info(message)
|
||||
}
|
||||
}
|
||||
|
||||
export function showMessageError(message) {
|
||||
if (isMobile()) {
|
||||
showFailToast(message)
|
||||
} else {
|
||||
ElMessage.error(message)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||
// * Use of this source code is governed by a Apache-2.0 license
|
||||
// * that can be found in the LICENSE file.
|
||||
// * @Author yangjian102621@163.com
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import axios from 'axios'
|
||||
import {getAdminToken, getSessionId, getUserToken} from "@/store/session";
|
||||
import {getAdminToken, getSessionId, getUserToken, removeAdminToken, removeUserToken} from "@/store/session";
|
||||
|
||||
axios.defaults.timeout = 180000
|
||||
axios.defaults.baseURL = process.env.VUE_APP_API_HOST
|
||||
@@ -22,9 +29,14 @@ axios.interceptors.response.use(
|
||||
let data = response.data;
|
||||
if (data.code === 0) {
|
||||
return response
|
||||
} else {
|
||||
return Promise.reject(response.data)
|
||||
} else if (data.code === 400) {
|
||||
if (response.request.responseURL.indexOf("/api/admin") !== -1) {
|
||||
removeAdminToken()
|
||||
} else {
|
||||
removeUserToken()
|
||||
}
|
||||
}
|
||||
return Promise.reject(response.data)
|
||||
}, error => {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||
// * Use of this source code is governed by a Apache-2.0 license
|
||||
// * that can be found in the LICENSE file.
|
||||
// * @Author yangjian102621@163.com
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
/**
|
||||
* Util lib functions
|
||||
*/
|
||||
import {showConfirmDialog} from "vant";
|
||||
|
||||
// generate a random string
|
||||
export function randString(length) {
|
||||
@@ -224,3 +232,15 @@ export function escapeHTML(html) {
|
||||
export function isIphone() {
|
||||
return /iPhone/i.test(navigator.userAgent) && !/iPad/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function showLoginDialog(router) {
|
||||
showConfirmDialog({
|
||||
title: '登录',
|
||||
message:
|
||||
'此操作需要登录才能进行,前往登录?',
|
||||
}).then(() => {
|
||||
router.push("/login")
|
||||
}).catch(() => {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<span class="name">{{ scope.item.name }}</span>
|
||||
<div class="opt">
|
||||
<div v-if="hasRole(scope.item.key)">
|
||||
<el-button size="small" type="success" @click="useRole(scope.item.id)">使用</el-button>
|
||||
<el-button size="small" type="success" @click="useRole(scope.item)">使用</el-button>
|
||||
<el-button size="small" type="danger" @click="updateRole(scope.item,'remove')">移除</el-button>
|
||||
</div>
|
||||
<el-button v-else size="small"
|
||||
@@ -110,8 +110,8 @@ const hasRole = (roleKey) => {
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const useRole = (roleId) => {
|
||||
router.push({name: "chat", params: {role_id: roleId}})
|
||||
const useRole = (role) => {
|
||||
router.push(`/chat?role_id=${role.id}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ const notice = ref("")
|
||||
const noticeKey = ref("SYSTEM_NOTICE")
|
||||
|
||||
if (isMobile()) {
|
||||
router.replace("/mobile")
|
||||
router.replace("/mobile/chat")
|
||||
}
|
||||
|
||||
// 获取系统配置
|
||||
@@ -330,7 +330,10 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.value = null
|
||||
if (socket.value !== null) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化数据
|
||||
@@ -355,9 +358,8 @@ const initData = () => {
|
||||
// 加载角色列表
|
||||
httpGet(`/api/role/list`).then((res) => {
|
||||
roles.value = res.data;
|
||||
console.log()
|
||||
if (router.currentRoute.value.params.role_id) {
|
||||
roleId.value = parseInt(router.currentRoute.value.params["role_id"])
|
||||
if (router.currentRoute.value.query.role_id) {
|
||||
roleId.value = parseInt(router.currentRoute.value.query.role_id)
|
||||
} else {
|
||||
roleId.value = roles.value[0]['id']
|
||||
}
|
||||
@@ -652,55 +654,63 @@ const connect = function (chat_id, role_id) {
|
||||
});
|
||||
|
||||
_socket.addEventListener('message', event => {
|
||||
if (event.data instanceof Blob) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(event.data, "UTF-8");
|
||||
reader.onload = () => {
|
||||
const data = JSON.parse(String(reader.result));
|
||||
if (data.type === 'start') {
|
||||
console.log(data)
|
||||
chatData.value.push({
|
||||
type: "reply",
|
||||
id: randString(32),
|
||||
icon: _role['icon'],
|
||||
content: ""
|
||||
});
|
||||
} else if (data.type === 'end') { // 消息接收完毕
|
||||
// 追加当前会话到会话列表
|
||||
if (isNewChat && newChatItem.value !== null) {
|
||||
newChatItem.value['title'] = previousText.value;
|
||||
newChatItem.value['chat_id'] = chat_id;
|
||||
chatList.value.unshift(newChatItem.value);
|
||||
activeChat.value = newChatItem.value;
|
||||
newChatItem.value = null; // 只追加一次
|
||||
}
|
||||
try {
|
||||
if (event.data instanceof Blob) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(event.data, "UTF-8");
|
||||
reader.onload = () => {
|
||||
const data = JSON.parse(String(reader.result));
|
||||
if (data.type === 'start') {
|
||||
chatData.value.push({
|
||||
type: "reply",
|
||||
id: randString(32),
|
||||
icon: _role['icon'],
|
||||
content: ""
|
||||
});
|
||||
} else if (data.type === 'end') { // 消息接收完毕
|
||||
// 追加当前会话到会话列表
|
||||
if (isNewChat && newChatItem.value !== null) {
|
||||
newChatItem.value['title'] = previousText.value;
|
||||
newChatItem.value['chat_id'] = chat_id;
|
||||
chatList.value.unshift(newChatItem.value);
|
||||
activeChat.value = newChatItem.value;
|
||||
newChatItem.value = null; // 只追加一次
|
||||
}
|
||||
|
||||
enableInput()
|
||||
lineBuffer.value = ''; // 清空缓冲
|
||||
enableInput()
|
||||
lineBuffer.value = ''; // 清空缓冲
|
||||
|
||||
// 获取 token
|
||||
const reply = chatData.value[chatData.value.length - 1]
|
||||
httpPost("/api/chat/tokens", {text: "", model: getModelValue(modelID.value), chat_id: chat_id}).then(res => {
|
||||
reply['created_at'] = new Date().getTime();
|
||||
reply['tokens'] = res.data;
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||
// 获取 token
|
||||
const reply = chatData.value[chatData.value.length - 1]
|
||||
httpPost("/api/chat/tokens", {
|
||||
text: "",
|
||||
model: getModelValue(modelID.value),
|
||||
chat_id: chat_id
|
||||
}).then(res => {
|
||||
reply['created_at'] = new Date().getTime();
|
||||
reply['tokens'] = res.data;
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
|
||||
} else {
|
||||
lineBuffer.value += data.content;
|
||||
const reply = chatData.value[chatData.value.length - 1]
|
||||
reply['orgContent'] = lineBuffer.value;
|
||||
reply['content'] = md.render(processContent(lineBuffer.value));
|
||||
}
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||
localStorage.setItem("chat_id", chat_id)
|
||||
})
|
||||
};
|
||||
} else {
|
||||
lineBuffer.value += data.content;
|
||||
const reply = chatData.value[chatData.value.length - 1]
|
||||
reply['orgContent'] = lineBuffer.value;
|
||||
reply['content'] = md.render(processContent(lineBuffer.value));
|
||||
}
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||
localStorage.setItem("chat_id", chat_id)
|
||||
})
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
});
|
||||
@@ -901,14 +911,6 @@ const exportChat = () => {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
const getChatById = (chatId) => {
|
||||
for (let index in chatList.value) {
|
||||
if (chatList.value[index].chat_id === chatId) {
|
||||
return chatList.value[index]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const getModelValue = (model_id) => {
|
||||
for (let i = 0; i < models.value.length; i++) {
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</div>
|
||||
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list" v-loading="loading" element-loading-background="rgba(0, 0, 0, 0.5)">
|
||||
<div class="finish-job-list">
|
||||
<div v-if="finishedJobs.length > 0">
|
||||
<ItemList :items="finishedJobs" :width="240" :gap="16">
|
||||
<template #default="scope">
|
||||
@@ -272,7 +272,6 @@ const initData = () => {
|
||||
fetchFinishJobs(1)
|
||||
connect()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -341,6 +340,9 @@ const connect = () => {
|
||||
}
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
}
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/dall/jobs?status=0`).then(res => {
|
||||
const jobs = res.data
|
||||
@@ -367,10 +369,11 @@ const fetchRunningJobs = () => {
|
||||
const page = ref(1)
|
||||
const pageSize = ref(15)
|
||||
const isOver = ref(false)
|
||||
const loading = ref(false)
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
}
|
||||
httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
@@ -380,9 +383,7 @@ const fetchFinishJobs = (page) => {
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(res.data)
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
loading.value = false
|
||||
ElMessage.error("获取任务失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@
|
||||
</div>
|
||||
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list" v-loading="loading" element-loading-background="rgba(0, 0, 0, 0.5)">
|
||||
<div class="finish-job-list">
|
||||
<div v-if="finishedJobs.length > 0">
|
||||
<ItemList :items="finishedJobs" :width="240" :gap="16">
|
||||
<template #default="scope">
|
||||
@@ -593,7 +593,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
|
||||
import {onMounted, onUnmounted, ref} from "vue"
|
||||
import {ChromeFilled, Delete, DocumentCopy, InfoFilled, Picture, Plus, UploadFilled} from "@element-plus/icons-vue";
|
||||
import Compressor from "compressorjs";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
@@ -760,7 +760,11 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.value = null
|
||||
clipboard.value.destroy()
|
||||
if (socket.value !== null) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化数据
|
||||
@@ -778,10 +782,6 @@ const initData = () => {
|
||||
});
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
})
|
||||
|
||||
const mjPower = ref(1)
|
||||
const mjActionPower = ref(1)
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
@@ -793,6 +793,10 @@ httpGet("/api/config/get?key=system").then(res => {
|
||||
|
||||
// 获取运行中的任务
|
||||
const fetchRunningJobs = () => {
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
}
|
||||
|
||||
httpGet(`/api/mj/jobs?status=0`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
@@ -832,9 +836,11 @@ const handleScrollEnd = () => {
|
||||
const page = ref(1)
|
||||
const pageSize = ref(15)
|
||||
const isOver = ref(false)
|
||||
const loading = ref(false)
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取已完成的任务
|
||||
httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
const jobs = res.data
|
||||
@@ -861,9 +867,7 @@ const fetchFinishJobs = (page) => {
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(jobs)
|
||||
}
|
||||
nextTick(() => loading.value = false)
|
||||
}).catch(e => {
|
||||
loading.value = false
|
||||
ElMessage.error("获取任务失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
<el-empty :image-size="100" v-else/>
|
||||
</div>
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list" v-loading="loading" element-loading-background="rgba(0, 0, 0, 0.5)">
|
||||
<div class="finish-job-list">
|
||||
<div v-if="finishedJobs.length > 0">
|
||||
<ItemList :items="finishedJobs" :width="240" :gap="16">
|
||||
<template #default="scope">
|
||||
@@ -616,7 +616,10 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
socket.value = null
|
||||
if (socket.value !== null) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -629,11 +632,14 @@ const initData = () => {
|
||||
fetchFinishJobs()
|
||||
connect()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
});
|
||||
}
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/sd/jobs?status=0`).then(res => {
|
||||
const jobs = res.data
|
||||
@@ -668,10 +674,11 @@ const handleScrollEnd = () => {
|
||||
const page = ref(1)
|
||||
const pageSize = ref(15)
|
||||
const isOver = ref(false)
|
||||
const loading = ref(false)
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
}
|
||||
httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
@@ -681,9 +688,7 @@ const fetchFinishJobs = (page) => {
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(res.data)
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
loading.value = false
|
||||
ElMessage.error("获取任务失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<h2>AI 绘画作品墙</h2>
|
||||
<div class="settings">
|
||||
<el-radio-group v-model="imgType" @change="changeImgType">
|
||||
<el-radio label="mj" size="large">MidJourney</el-radio>
|
||||
<el-radio label="sd" size="large">Stable Diffusion</el-radio>
|
||||
<el-radio label="dall" size="large">DALL-E</el-radio>
|
||||
<el-radio value="mj" size="large">MidJourney</el-radio>
|
||||
<el-radio value="sd" size="large">Stable Diffusion</el-radio>
|
||||
<el-radio value="dall" size="large">DALL-E</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@
|
||||
</template>
|
||||
</v3-waterfall>
|
||||
|
||||
<v3-waterfall v-if="imgType === 'dall'"
|
||||
<v3-waterfall v-else-if="imgType === 'dall'"
|
||||
id="waterfall"
|
||||
:list="data['dall']"
|
||||
srcKey="img_thumb"
|
||||
@@ -338,7 +338,6 @@ const getNext = () => {
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
let url = ""
|
||||
console.log(imgType.value)
|
||||
switch (imgType.value) {
|
||||
case "mj":
|
||||
url = "/api/mj/imgWall"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="index-page" :style="{height: winHeight+'px'}">
|
||||
<div class="bg"></div>
|
||||
<div class="menu-box">
|
||||
<el-menu
|
||||
mode="horizontal"
|
||||
@@ -23,6 +24,8 @@
|
||||
<span>项目源码</span>
|
||||
</el-button>
|
||||
</a>
|
||||
<el-button @click="router.push('/login')" round>登录</el-button>
|
||||
<el-button @click="router.push('/register')" round>注册</el-button>
|
||||
</div>
|
||||
</el-menu>
|
||||
</div>
|
||||
@@ -31,22 +34,22 @@
|
||||
<p>{{ slogan }}</p>
|
||||
<el-button @click="router.push('/chat')" color="#ffffff" style="color:#007bff" :dark="false">
|
||||
<i class="iconfont icon-chat"></i>
|
||||
<span>AI聊天</span>
|
||||
<span>AI 对话</span>
|
||||
</el-button>
|
||||
<el-button @click="router.push('/mj')" color="#C4CCFD" style="color:#424282" :dark="false">
|
||||
<i class="iconfont icon-mj"></i>
|
||||
<span>AI-MJ绘画</span>
|
||||
<span>MJ 绘画</span>
|
||||
</el-button>
|
||||
|
||||
<el-button @click="router.push('/sd')" color="#4AE6DF" style="color:#424282" :dark="false">
|
||||
<i class="iconfont icon-sd"></i>
|
||||
<span>AI-SD绘画</span>
|
||||
<span>SD 绘画</span>
|
||||
</el-button>
|
||||
<el-button @click="router.push('/xmind')" color="#FFFD55" style="color:#424282" :dark="false">
|
||||
<i class="iconfont icon-xmind"></i>
|
||||
<span>思维导图</span>
|
||||
</el-button>
|
||||
<div id="animation-container"></div>
|
||||
<!-- <div id="animation-container"></div>-->
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
@@ -57,19 +60,24 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
import * as THREE from 'three';
|
||||
// import * as THREE from 'three';
|
||||
import {onMounted, ref} from "vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import FooterBar from "@/components/FooterBar.vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {isMobile} from "@/utils/libs";
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
if (isMobile()) {
|
||||
router.push("/mobile")
|
||||
}
|
||||
|
||||
const title = ref("Geek-AI 创作系统")
|
||||
const logo = ref("/images/logo.png")
|
||||
const slogan = ref("我辈之人,先干为敬,陪您先把 AI 用起来")
|
||||
const size = Math.max(window.innerWidth * 0.5, window.innerHeight * 0.8)
|
||||
// const size = Math.max(window.innerWidth * 0.5, window.innerHeight * 0.8)
|
||||
const winHeight = window.innerHeight - 150
|
||||
|
||||
onMounted(() => {
|
||||
@@ -83,59 +91,59 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
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('#007bff'), // 高光颜色
|
||||
});
|
||||
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();
|
||||
}
|
||||
);
|
||||
// // 创建场景
|
||||
// // 创建场景
|
||||
// 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>
|
||||
|
||||
@@ -143,7 +151,6 @@ const init = () => {
|
||||
@import '@/assets/iconfont/iconfont.css'
|
||||
.index-page {
|
||||
margin: 0
|
||||
background-color #007bff /* 科技蓝色背景 */
|
||||
overflow hidden
|
||||
color #ffffff
|
||||
display flex
|
||||
@@ -151,6 +158,18 @@ const init = () => {
|
||||
align-items baseline
|
||||
padding-top 150px
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.menu-box {
|
||||
position absolute
|
||||
top 0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="main">
|
||||
<div class="contain">
|
||||
<div class="logo">
|
||||
<el-image :src="logo" fit="cover"/>
|
||||
<el-image :src="logo" fit="cover" @click="router.push('/')"/>
|
||||
</div>
|
||||
<div class="header">{{ title }}</div>
|
||||
<div class="content">
|
||||
@@ -34,9 +34,10 @@
|
||||
<el-button class="login-btn" size="large" type="primary" @click="login">登录</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-row class="text-line" gutter="20">
|
||||
<el-button type="primary" @click="router.push('/register')" size="small" plain>注册新账号</el-button>
|
||||
<el-button type="success" @click="showResetPass = true" size="small" plain>重置密码</el-button>
|
||||
<el-row class="opt" :gutter="20">
|
||||
<el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
|
||||
<el-col :span="8"><el-link @click="showResetPass = true">重置密码</el-link></el-col>
|
||||
<el-col :span="8"><el-link @click="router.push('/')">首页</el-link></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,17 +56,16 @@
|
||||
import {ref} from "vue";
|
||||
import {Lock, UserFilled} from "@element-plus/icons-vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useRouter} from "vue-router";
|
||||
import FooterBar from "@/components/FooterBar.vue";
|
||||
import {isMobile} from "@/utils/libs";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {setUserToken} from "@/store/session";
|
||||
import {prevRoute} from "@/router";
|
||||
import ResetPass from "@/components/ResetPass.vue";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
|
||||
const router = useRouter();
|
||||
const title = ref('ChatPlus 用户登录');
|
||||
const title = ref('Geek-AI');
|
||||
const username = ref(process.env.VUE_APP_USER);
|
||||
const password = ref(process.env.VUE_APP_PASS);
|
||||
const showResetPass = ref(false)
|
||||
@@ -74,8 +74,9 @@ const logo = ref("/images/logo.png")
|
||||
// 获取系统配置
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
logo.value = res.data.logo
|
||||
title.value = res.data.title
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
showMessageError("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
|
||||
@@ -96,30 +97,25 @@ const handleKeyup = (e) => {
|
||||
|
||||
const login = function () {
|
||||
if (username.value.trim() === '') {
|
||||
return ElMessage.error("请输入用户民")
|
||||
return showMessageError("请输入用户民")
|
||||
}
|
||||
if (password.value.trim() === '') {
|
||||
return ElMessage.error('请输入密码');
|
||||
return showMessageError('请输入密码');
|
||||
}
|
||||
|
||||
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
||||
setUserToken(res.data)
|
||||
if (prevRoute.path === '' || prevRoute.path === '/register') {
|
||||
if (isMobile()) {
|
||||
router.push('/mobile')
|
||||
} else {
|
||||
router.push('/chat')
|
||||
}
|
||||
if (isMobile()) {
|
||||
router.push('/mobile')
|
||||
} else {
|
||||
router.push(prevRoute.path)
|
||||
router.push('/chat')
|
||||
}
|
||||
|
||||
}).catch((e) => {
|
||||
ElMessage.error('登录失败,' + e.message)
|
||||
showMessageError('登录失败,' + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@@ -154,6 +150,7 @@ const login = function () {
|
||||
|
||||
.el-image {
|
||||
width 120px;
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +161,7 @@ const login = function () {
|
||||
color $white_v1
|
||||
letter-space 2px
|
||||
text-align center
|
||||
padding-top 10px
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -198,6 +196,13 @@ const login = function () {
|
||||
padding-top 10px;
|
||||
font-size 14px;
|
||||
}
|
||||
|
||||
.opt {
|
||||
padding 15px
|
||||
.el-col {
|
||||
text-align center
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
import LoginDialog from "@/components/LoginDialog.vue";
|
||||
import {nextTick, onMounted, onUnmounted, ref} from 'vue';
|
||||
import {Markmap} from 'markmap-view';
|
||||
import {loadCSS, loadJS} from 'markmap-common';
|
||||
import {Transformer} from 'markmap-lib';
|
||||
import {checkSession} from "@/action/session";
|
||||
import {httpGet} from "@/utils/http";
|
||||
@@ -129,9 +128,6 @@ const showLoginDialog = ref(false)
|
||||
const isLogin = ref(false)
|
||||
const loginUser = ref({power: 0})
|
||||
const transformer = new Transformer();
|
||||
const {scripts, styles} = transformer.getAssets()
|
||||
loadCSS(styles);
|
||||
loadJS(scripts);
|
||||
|
||||
|
||||
const svgRef = ref(null)
|
||||
@@ -142,8 +138,12 @@ const loading = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
initData()
|
||||
markMap.value = Markmap.create(svgRef.value)
|
||||
update()
|
||||
try {
|
||||
markMap.value = Markmap.create(svgRef.value)
|
||||
update()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
});
|
||||
|
||||
const initData = () => {
|
||||
@@ -168,9 +168,13 @@ const initData = () => {
|
||||
|
||||
const update = () => {
|
||||
|
||||
const {root} = transformer.transform(processContent(text.value))
|
||||
markMap.value.setData(root)
|
||||
markMap.value.fit()
|
||||
try {
|
||||
const {root} = transformer.transform(processContent(text.value))
|
||||
markMap.value.setData(root)
|
||||
markMap.value.fit()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const processContent = (text) => {
|
||||
@@ -230,7 +234,6 @@ const connect = (userId) => {
|
||||
const _socket = new WebSocket(host + `/api/markMap/client?user_id=${userId}&model_id=${modelID.value}`);
|
||||
_socket.addEventListener('open', () => {
|
||||
socket.value = _socket;
|
||||
|
||||
// 发送心跳消息
|
||||
sendHeartbeat()
|
||||
});
|
||||
@@ -265,9 +268,10 @@ const connect = (userId) => {
|
||||
|
||||
_socket.addEventListener('close', () => {
|
||||
loading.value = false
|
||||
if (socket.value !== null) {
|
||||
checkSession().then(() => {
|
||||
connect(userId)
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,29 +5,100 @@
|
||||
<div class="page-inner">
|
||||
<div class="contain" v-if="enableRegister">
|
||||
<div class="logo">
|
||||
<el-image :src="logo" fit="cover"/>
|
||||
<el-image :src="logo" fit="cover" @click="router.push('/')"/>
|
||||
</div>
|
||||
|
||||
<div class="header">{{ title }}</div>
|
||||
<div class="content">
|
||||
<el-form :model="formData" label-width="120px" ref="formRef">
|
||||
<div class="block">
|
||||
<el-input :placeholder="placeholder"
|
||||
size="large"
|
||||
v-model="formData.username"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Iphone/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-form :model="data" class="form" v-if="enableRegister">
|
||||
<el-tabs v-model="activeName" class="demo-tabs">
|
||||
<el-tab-pane label="手机注册" name="mobile" v-if="enableMobile">
|
||||
<div class="block">
|
||||
<el-input placeholder="手机号码"
|
||||
size="large"
|
||||
v-model="data.username"
|
||||
maxlength="11"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Iphone/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="block">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-input placeholder="验证码"
|
||||
size="large" maxlength="30"
|
||||
v-model="data.code"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Checked/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<send-msg size="large" :receiver="data.username"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="邮箱注册" name="email" v-if="enableEmail">
|
||||
<div class="block">
|
||||
<el-input placeholder="邮箱地址"
|
||||
size="large"
|
||||
v-model="data.username"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Message/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="block">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-input placeholder="验证码"
|
||||
size="large" maxlength="30"
|
||||
v-model="data.code"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Checked/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<send-msg size="large" :receiver="data.username"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="用户名注册" name="username" v-if="enableUser">
|
||||
<div class="block">
|
||||
<el-input placeholder="用户名"
|
||||
size="large"
|
||||
v-model="data.username"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Iphone/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="block">
|
||||
<el-input placeholder="请输入密码(8-16位)"
|
||||
maxlength="16" size="large"
|
||||
v-model="formData.password" show-password
|
||||
v-model="data.password" show-password
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
@@ -39,7 +110,7 @@
|
||||
|
||||
<div class="block">
|
||||
<el-input placeholder="重复密码(8-16位)"
|
||||
size="large" maxlength="16" v-model="formData.repass" show-password
|
||||
size="large" maxlength="16" v-model="data.repass" show-password
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
@@ -49,30 +120,10 @@
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<div class="block" v-if="enableMobile || enableEmail">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-input placeholder="验证码"
|
||||
size="large" maxlength="30"
|
||||
v-model="formData.code"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Checked/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<send-msg size="large" :receiver="formData.username"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<el-input placeholder="邀请码"
|
||||
<el-input placeholder="邀请码(可选)"
|
||||
size="large"
|
||||
v-model="formData.invite_code"
|
||||
v-model="data.invite_code"
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
@@ -82,8 +133,10 @@
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<el-row class="btn-row">
|
||||
<el-button class="login-btn" size="large" type="primary" @click="register">注册</el-button>
|
||||
<el-row class="btn-row" :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-button class="login-btn" type="primary" size="large" @click="submitRegister">注册</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row class="text-line">
|
||||
@@ -91,6 +144,8 @@
|
||||
<el-link type="primary" @click="router.push('/login')">登录</el-link>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -124,78 +179,86 @@ import FooterBar from "@/components/FooterBar.vue";
|
||||
import SendMsg from "@/components/SendMsg.vue";
|
||||
import {arrayContains} from "@/utils/libs";
|
||||
import {setUserToken} from "@/store/session";
|
||||
import {validateEmail, validateMobile} from "@/utils/validate";
|
||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
||||
|
||||
const router = useRouter();
|
||||
const title = ref('ChatPlus 用户注册');
|
||||
const formData = ref({
|
||||
const title = ref('Geek-AI 用户注册');
|
||||
const logo = ref("/images/logo")
|
||||
const data = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
code: '',
|
||||
repass: '',
|
||||
invite_code: router.currentRoute.value.query['invite_code'],
|
||||
})
|
||||
const formRef = ref(null)
|
||||
|
||||
const enableMobile = ref(false)
|
||||
const enableEmail = ref(false)
|
||||
const enableRegister = ref(true)
|
||||
const enableUser = ref(false)
|
||||
const enableRegister = ref(false)
|
||||
const activeName = ref("mobile")
|
||||
const wxImg = ref("/images/wx.png")
|
||||
const ways = []
|
||||
const placeholder = ref("用户名:")
|
||||
const logo = ref("/images/logo.png")
|
||||
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
if (res.data) {
|
||||
title.value = res.data.title
|
||||
logo.value = res.data.logo
|
||||
const registerWays = res.data['register_ways']
|
||||
if (arrayContains(registerWays, "mobile")) {
|
||||
enableMobile.value = true
|
||||
ways.push("手机号")
|
||||
}
|
||||
if (arrayContains(registerWays, "email")) {
|
||||
enableEmail.value = true
|
||||
ways.push("邮箱地址")
|
||||
}
|
||||
placeholder.value += ways.join("/")
|
||||
if (arrayContains(registerWays, "username")) {
|
||||
enableUser.value = true
|
||||
}
|
||||
// 是否启用注册
|
||||
enableRegister.value = res.data['enabled_register']
|
||||
// 覆盖微信二维码
|
||||
// 使用后台上传的客服微信二维码
|
||||
if (res.data['wechat_card_url'] !== '') {
|
||||
wxImg.value = res.data['wechat_card_url']
|
||||
}
|
||||
logo.value = res.data.logo
|
||||
}
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/invite/hits", {code: formData.value.invite_code}).then(() => {
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
|
||||
const register = function () {
|
||||
if (formData.value.username === '') {
|
||||
return ElMessage.error('请输入用户名');
|
||||
// 注册操作
|
||||
const submitRegister = () => {
|
||||
if (data.value.username === '') {
|
||||
return showMessageError('请输入用户名');
|
||||
}
|
||||
|
||||
if (formData.value.password.length < 8) {
|
||||
return ElMessage.error('密码的长度为8-16个字符');
|
||||
}
|
||||
if (formData.value.repass !== formData.value.password) {
|
||||
return ElMessage.error('两次输入密码不一致');
|
||||
if (activeName.value === 'mobile' && !validateMobile(data.value.username)) {
|
||||
return showMessageError('请输入合法的手机号');
|
||||
}
|
||||
|
||||
if ((enableEmail.value || enableMobile.value) && formData.value.code === '') {
|
||||
return ElMessage.error('请输入验证码');
|
||||
if (activeName.value === 'email' && !validateEmail(data.value.username)) {
|
||||
return showMessageError('请输入合法的邮箱地址');
|
||||
}
|
||||
httpPost('/api/user/register', formData.value).then((res) => {
|
||||
|
||||
if (data.value.password.length < 8) {
|
||||
return showMessageError('密码的长度为8-16个字符');
|
||||
}
|
||||
if (data.value.repass !== data.value.password) {
|
||||
return showMessageError('两次输入密码不一致');
|
||||
}
|
||||
|
||||
if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') {
|
||||
return showMessageError('请输入验证码');
|
||||
}
|
||||
data.value.reg_way = activeName.value
|
||||
httpPost('/api/user/register', data.value).then((res) => {
|
||||
setUserToken(res.data)
|
||||
ElMessage.success({
|
||||
showMessageOK({
|
||||
"message": "注册成功,即将跳转到对话主界面...",
|
||||
onClose: () => router.push("/chat"),
|
||||
duration: 1000
|
||||
})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('注册失败,' + e.message)
|
||||
showMessageError('注册失败,' + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -222,7 +285,7 @@ const register = function () {
|
||||
|
||||
.page-inner {
|
||||
max-width 450px
|
||||
min-width 360px
|
||||
width 100%
|
||||
height 100vh
|
||||
display flex
|
||||
justify-content center
|
||||
@@ -234,13 +297,14 @@ const register = function () {
|
||||
color #ffffff
|
||||
border-radius 10px;
|
||||
z-index 10
|
||||
background-color rgba(255, 255, 255, 0.3)
|
||||
background-color rgba(255, 255, 255, 0.2)
|
||||
|
||||
.logo {
|
||||
text-align center
|
||||
|
||||
.el-image {
|
||||
width 120px;
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +315,7 @@ const register = function () {
|
||||
color $white_v1
|
||||
letter-space 2px
|
||||
text-align center
|
||||
padding-top 10px
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -326,5 +391,9 @@ const register = function () {
|
||||
color #c1c1c1
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
color #ffffff
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -284,15 +284,8 @@ const changePlatform = () => {
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.list {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content flex-end
|
||||
|
||||
.el-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
}
|
||||
|
||||
.copy-key {
|
||||
@@ -303,6 +296,12 @@ const changePlatform = () => {
|
||||
.el-select {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content righ
|
||||
}
|
||||
}
|
||||
|
||||
.el-form {
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat, processContent, removeArrayItem} from "@/utils/libs";
|
||||
import {dateFormat, processContent} from "@/utils/libs";
|
||||
import {Search} from "@element-plus/icons-vue";
|
||||
import 'highlight.js/styles/a11y-dark.css'
|
||||
import hl from "highlight.js";
|
||||
@@ -343,8 +343,8 @@ const showMessages = (row) => {
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.chat-list {
|
||||
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
@@ -365,6 +365,12 @@ const showMessages = (row) => {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
overflow-y: auto;
|
||||
overflow-x hidden
|
||||
|
||||
@@ -350,13 +350,14 @@ const remove = function (row) {
|
||||
@import "@/assets/css/admin/form.styl";
|
||||
.model-list {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content flex-end
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
margin-right: 5px;
|
||||
.cell {
|
||||
.copy-model {
|
||||
margin-left 6px
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,5 +372,11 @@ const remove = function (row) {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="container role-list" v-loading="loading">
|
||||
<div class="container function" v-loading="loading">
|
||||
<div class="handle-box">
|
||||
<el-button type="primary" :icon="Plus" @click="addRow">新增</el-button>
|
||||
</div>
|
||||
@@ -296,15 +296,9 @@ const generateToken = () => {
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.role-list {
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display flex;
|
||||
justify-content flex-end
|
||||
|
||||
.el-icon {
|
||||
margin-right 5px;
|
||||
}
|
||||
.function {
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
}
|
||||
|
||||
.param-line {
|
||||
@@ -334,5 +328,11 @@ const generateToken = () => {
|
||||
text-align center
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="admin-home" v-if="isLogin">
|
||||
<admin-sidebar/>
|
||||
<admin-sidebar v-model:theme="theme"/>
|
||||
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
|
||||
<admin-header/>
|
||||
<admin-tags/>
|
||||
<div class="content">
|
||||
<admin-header v-model:theme="theme" @changeTheme="changeTheme"/>
|
||||
<admin-tags v-model:theme="theme"/>
|
||||
<div :class="'content '+theme" :style="{height:contentHeight+'px'}">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="move" mode="out-in">
|
||||
<keep-alive :include="tags.nameList">
|
||||
@@ -25,10 +25,13 @@ import AdminTags from "@/components/admin/AdminTags.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {checkAdminSession} from "@/action/session";
|
||||
import {ref} from "vue";
|
||||
import {getAdminTheme, setAdminTheme} from "@/store/system";
|
||||
|
||||
const sidebar = useSidebarStore();
|
||||
const tags = useTagsStore();
|
||||
const isLogin = ref(false)
|
||||
const contentHeight = window.innerHeight - 80
|
||||
const theme = ref(getAdminTheme())
|
||||
|
||||
// 获取会话信息
|
||||
const router = useRouter();
|
||||
@@ -37,10 +40,20 @@ checkAdminSession().then(() => {
|
||||
}).catch(() => {
|
||||
router.replace('/admin/login')
|
||||
})
|
||||
|
||||
const changeTheme = (value) => {
|
||||
if (value) {
|
||||
theme.value = 'dark'
|
||||
} else {
|
||||
theme.value = 'light'
|
||||
}
|
||||
setAdminTheme(theme.value)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
@import '@/assets/css/main.css';
|
||||
@import '@/assets/css/color-dark.css';
|
||||
@import '@/assets/css/color-dark.styl';
|
||||
@import '@/assets/css/main.styl';
|
||||
@import '@/assets/iconfont/iconfont.css';
|
||||
</style>
|
||||
|
||||
@@ -63,19 +63,19 @@ const fetchList = function (_page, _pageSize) {
|
||||
<style lang="stylus" scoped>
|
||||
.list {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display flex;
|
||||
justify-content flex-start
|
||||
|
||||
.el-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -74,7 +74,6 @@ import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {dateFormat, removeArrayItem} from "@/utils/libs";
|
||||
import {Plus} from "@element-plus/icons-vue";
|
||||
import {Sortable} from "sortablejs";
|
||||
|
||||
// 变量定义
|
||||
const items = ref([])
|
||||
@@ -170,19 +169,19 @@ const remove = function (row) {
|
||||
<style lang="stylus" scoped>
|
||||
.list {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display flex;
|
||||
justify-content flex-end
|
||||
|
||||
.el-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -129,6 +129,7 @@ const remove = function (row) {
|
||||
.order {
|
||||
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
@@ -149,5 +150,11 @@ const remove = function (row) {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {Search} from "@element-plus/icons-vue";
|
||||
@@ -135,6 +135,7 @@ const fetchData = () => {
|
||||
<style lang="stylus" scoped>
|
||||
.power-log {
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
@@ -155,5 +156,12 @@ const fetchData = () => {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
width 100%
|
||||
justify-content right
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -207,6 +207,14 @@ const remove = function (row) {
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.product {
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
@@ -222,5 +230,11 @@ const remove = function (row) {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -82,6 +82,14 @@ const remove = function (row) {
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.list {
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
@@ -97,5 +105,11 @@ const remove = function (row) {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -322,6 +322,15 @@ const uploadImg = (file) => {
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.role-list {
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display flex;
|
||||
@@ -360,5 +369,11 @@ const uploadImg = (file) => {
|
||||
text-align center
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
justify-content right
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -258,6 +258,52 @@
|
||||
<Menu/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="授权激活" name="license">
|
||||
<div class="container">
|
||||
<el-descriptions
|
||||
v-if="license.is_active"
|
||||
class="margin-top"
|
||||
title="授权信息"
|
||||
:column="3"
|
||||
border
|
||||
>
|
||||
<el-descriptions-item :span="3" :width="150">
|
||||
<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>
|
||||
{{ dateFormat(license.expired_at) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">用户人数</div>
|
||||
</template>
|
||||
{{ license.user_num }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">机器码</div>
|
||||
</template>
|
||||
{{ license.machine_id }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-form :model="system" label-width="150px" label-position="right">
|
||||
<el-form-item label="许可授权码" prop="license">
|
||||
<el-input v-model="licenseKey"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="active">立即激活</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
@@ -271,6 +317,7 @@ import {InfoFilled, UploadFilled} 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";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
|
||||
const activeName = ref('basic')
|
||||
const system = ref({models: []})
|
||||
@@ -279,6 +326,7 @@ const systemFormRef = ref(null)
|
||||
const models = ref([])
|
||||
const openAIModels = ref([])
|
||||
const notice = ref("")
|
||||
const license = ref({is_active: false})
|
||||
|
||||
onMounted(() => {
|
||||
// 加载系统配置
|
||||
@@ -302,8 +350,17 @@ onMounted(() => {
|
||||
ElMessage.error("获取模型失败:" + e.message)
|
||||
})
|
||||
|
||||
fetchLicense()
|
||||
})
|
||||
|
||||
const fetchLicense = () => {
|
||||
httpGet("/api/admin/config/get/license").then(res => {
|
||||
license.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取 License 失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const rules = reactive({
|
||||
title: [{required: true, message: '请输入网站标题', trigger: 'blur',}],
|
||||
admin_title: [{required: true, message: '请输入控制台标题', trigger: 'blur',}],
|
||||
@@ -331,6 +388,20 @@ const save = function (key) {
|
||||
}
|
||||
}
|
||||
|
||||
// 激活授权
|
||||
const licenseKey = ref("")
|
||||
const active = () => {
|
||||
if (licenseKey.value === "") {
|
||||
return ElMessage.error("请输入授权码")
|
||||
}
|
||||
httpPost("/api/admin/active", {license: licenseKey.value}).then(res => {
|
||||
ElMessage.success("授权成功,机器编码为:" + res.data)
|
||||
fetchLicense()
|
||||
}).catch(e => {
|
||||
ElMessage.error(e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const configKey = ref("")
|
||||
const beforeUpload = (key) => {
|
||||
configKey.value = key
|
||||
@@ -375,10 +446,9 @@ const onUploadImg = (files, callback) => {
|
||||
}).catch(e => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
})
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@@ -389,10 +459,9 @@ const onUploadImg = (files, callback) => {
|
||||
|
||||
.el-tabs {
|
||||
width 100%
|
||||
background-color #ffffff
|
||||
background-color var(--el-bg-color)
|
||||
padding 10px 20px 40px 20px
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px
|
||||
border: 1px solid var(--el-border-color);
|
||||
|
||||
.container {
|
||||
.el-form {
|
||||
@@ -425,6 +494,10 @@ const onUploadImg = (files, callback) => {
|
||||
}
|
||||
}
|
||||
|
||||
.el-descriptions {
|
||||
margin-bottom 20px
|
||||
}
|
||||
|
||||
.el-alert {
|
||||
margin-bottom 15px;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<el-input v-model="user.username" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="add" label="密码:" prop="password">
|
||||
<el-input v-model="user.password" autocomplete="off"/>
|
||||
<el-input v-model="user.password" autocomplete="off" placeholder="8-16位"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="剩余算力:" prop="power">
|
||||
<el-input v-model.number="user.power" autocomplete="off" placeholder="0"/>
|
||||
@@ -186,8 +186,17 @@ const models = ref([])
|
||||
const showUserEditDialog = ref(false)
|
||||
const showResetPassDialog = ref(false)
|
||||
const rules = reactive({
|
||||
username: [{required: true, message: '请输入账号', trigger: 'change',}],
|
||||
password: [{required: true, message: '请输入密码', trigger: 'change',}],
|
||||
username: [{required: true, message: '请输入账号', trigger: 'blur',}],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value) => {
|
||||
return !(value.length > 16 || value.length < 8);
|
||||
|
||||
}, message: '密码必须为8-16',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
calls: [
|
||||
{required: true, message: '请输入提问次数'},
|
||||
{type: 'number', message: '请输入有效数字'},
|
||||
@@ -323,6 +332,7 @@ const doResetPass = () => {
|
||||
.user-list {
|
||||
|
||||
.handle-box {
|
||||
margin-bottom 20px
|
||||
.handle-input {
|
||||
max-width 150px;
|
||||
margin-right 10px;
|
||||
@@ -341,6 +351,11 @@ const doResetPass = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding 20px 0
|
||||
display flex
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<div class="chat-export-mobile">
|
||||
<div class="chat-box">
|
||||
<van-sticky :offset-top="0" position="top">
|
||||
<van-nav-bar left-arrow left-text="返回" @click-left="router.back()">
|
||||
<template #title>
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="title">
|
||||
<van-cell center title="角色"> {{ role }}</van-cell>
|
||||
<van-cell center title="模型">{{ model }}</van-cell>
|
||||
</van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
</van-sticky>
|
||||
<van-nav-bar left-arrow left-text="返回" @click-left="router.back()">
|
||||
<template #title>
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="title">
|
||||
<van-cell center title="角色"> {{ role }}</van-cell>
|
||||
<van-cell center title="模型">{{ model }}</van-cell>
|
||||
</van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<div class="chat-list-wrapper">
|
||||
<div id="message-list-box" class="message-list-box">
|
||||
@@ -53,13 +51,14 @@ import {useRouter} from "vue-router";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import 'highlight.js/styles/a11y-dark.css'
|
||||
import hl from "highlight.js";
|
||||
import {showFailToast} from "vant";
|
||||
|
||||
const chatData = ref([])
|
||||
const router = useRouter()
|
||||
const chatId = router.currentRoute.value.query['chat_id']
|
||||
const title = router.currentRoute.value.query['title']
|
||||
const role = router.currentRoute.value.query['role']
|
||||
const model = router.currentRoute.value.query['model']
|
||||
const title = ref('')
|
||||
const role = ref('')
|
||||
const model = ref('')
|
||||
const finished = ref(false)
|
||||
const error = ref(false)
|
||||
|
||||
@@ -117,28 +116,42 @@ const onLoad = () => {
|
||||
error.value = true
|
||||
})
|
||||
|
||||
httpGet(`/api/chat/detail?chat_id=${chatId}`).then(res => {
|
||||
title.value = res.data.title
|
||||
model.value = res.data.model
|
||||
role.value = res.data.role_name
|
||||
}).catch(e => {
|
||||
showFailToast('加载对话失败:' + e.message)
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="stylus">
|
||||
.chat-export-mobile {
|
||||
background #F5F5F5;
|
||||
height 100vh
|
||||
|
||||
.van-nav-bar {
|
||||
position static
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
background #F5F5F5;
|
||||
|
||||
.message-list-box {
|
||||
background #F5F5F5;
|
||||
padding-top 50px
|
||||
padding-bottom: 10px
|
||||
.chat-list-wrapper {
|
||||
padding 10px 0 10px 0
|
||||
|
||||
.van-cell {
|
||||
background none
|
||||
.message-list-box {
|
||||
background #F5F5F5;
|
||||
padding-bottom: 10px
|
||||
|
||||
.van-cell {
|
||||
background none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.van-nav-bar__title {
|
||||
.van-dropdown-menu__title {
|
||||
margin-right 10px
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-background">
|
||||
<div v-if="isLogin" class="container mobile-chat-list">
|
||||
<div class="container mobile-chat-list">
|
||||
<van-nav-bar
|
||||
:title="title"
|
||||
left-text="新建会话"
|
||||
@@ -82,8 +82,7 @@ import {httpGet, httpPost} from "@/utils/http";
|
||||
import {showConfirmDialog, showFailToast, showSuccessToast} from "vant";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {router} from "@/router";
|
||||
import {setChatConfig} from "@/store/chat";
|
||||
import {removeArrayItem} from "@/utils/libs";
|
||||
import {removeArrayItem, showLoginDialog} from "@/utils/libs";
|
||||
|
||||
const title = ref("会话列表")
|
||||
const chatName = ref("")
|
||||
@@ -136,32 +135,56 @@ checkSession().then((user) => {
|
||||
})
|
||||
|
||||
}).catch(() => {
|
||||
router.push("/login")
|
||||
loading.value = false
|
||||
finished.value = true
|
||||
|
||||
// 加载角色列表
|
||||
httpGet('/api/role/list').then((res) => {
|
||||
if (res.data) {
|
||||
const items = res.data
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
// console.log(items[i])
|
||||
roles.value.push({
|
||||
text: items[i].name,
|
||||
value: items[i].id,
|
||||
icon: items[i].icon,
|
||||
helloMsg: items[i].hello_msg
|
||||
})
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
showFailToast("加载聊天角色失败")
|
||||
})
|
||||
|
||||
// 加载模型
|
||||
httpGet('/api/model/list').then(res => {
|
||||
if (res.data) {
|
||||
const items = res.data
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
models.value.push({text: items[i].name, value: items[i].id})
|
||||
}
|
||||
}
|
||||
}).catch(e => {
|
||||
showFailToast("加载模型失败: " + e.message)
|
||||
})
|
||||
})
|
||||
|
||||
const onLoad = () => {
|
||||
httpGet("/api/chat/list?user_id=" + loginUser.value.id).then((res) => {
|
||||
if (res.data) {
|
||||
chats.value = res.data;
|
||||
allChats.value = res.data;
|
||||
finished.value = true
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(() => {
|
||||
error.value = true
|
||||
showFailToast("加载会话列表失败")
|
||||
checkSession().then(() => {
|
||||
httpGet("/api/chat/list?user_id=" + loginUser.value.id).then((res) => {
|
||||
if (res.data) {
|
||||
chats.value = res.data;
|
||||
allChats.value = res.data;
|
||||
finished.value = true
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(() => {
|
||||
error.value = true
|
||||
showFailToast("加载会话列表失败")
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
const getModelValue = (model_id) => {
|
||||
for (let i = 0; i < models.value.length; i++) {
|
||||
if (models.value[i].value === model_id) {
|
||||
return models.value[i].text
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
if (chatName.value === '') {
|
||||
chats.value = allChats.value
|
||||
@@ -177,6 +200,10 @@ const search = () => {
|
||||
}
|
||||
|
||||
const clearAllChatHistory = () => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
|
||||
showConfirmDialog({
|
||||
title: '操作提示',
|
||||
message: '确定要删除所有的会话记录吗?'
|
||||
@@ -193,44 +220,50 @@ const clearAllChatHistory = () => {
|
||||
}
|
||||
|
||||
const newChat = (item) => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
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
|
||||
// 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}
|
||||
})
|
||||
router.push('/mobile/chat/session')
|
||||
}
|
||||
|
||||
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')
|
||||
// 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}`)
|
||||
}
|
||||
|
||||
const editChat = (row) => {
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<template>
|
||||
<div class="app-background">
|
||||
<div class="mobile-chat" v-loading="loading" element-loading-text="正在连接会话...">
|
||||
<van-sticky ref="navBarRef" :offset-top="0" position="top">
|
||||
<van-nav-bar left-arrow left-text="返回" @click-left="router.back()">
|
||||
<template #title>
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="title">
|
||||
<van-cell center title="角色"> {{ role.name }}</van-cell>
|
||||
<van-cell center title="模型">{{ modelValue }}</van-cell>
|
||||
</van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</template>
|
||||
<van-nav-bar ref="navBarRef">
|
||||
<template #title>
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="title">
|
||||
<van-cell center title="角色"> {{ role.name }}</van-cell>
|
||||
<van-cell center title="模型">{{ modelValue }}</van-cell>
|
||||
</van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</template>
|
||||
<template #left>
|
||||
<span class="setting">
|
||||
<van-icon name="setting-o" @click="showPicker = true"/>
|
||||
</span>
|
||||
</template>
|
||||
<template #right>
|
||||
<van-icon name="share-o" @click="showShare = true"/>
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<van-icon name="share-o" @click="showShare = true"/>
|
||||
</template>
|
||||
|
||||
</van-nav-bar>
|
||||
</van-sticky>
|
||||
</van-nav-bar>
|
||||
|
||||
<van-share-sheet
|
||||
v-model:show="showShare"
|
||||
@@ -38,23 +40,19 @@
|
||||
<chat-prompt
|
||||
v-if="item.type==='prompt'"
|
||||
:content="item.content"
|
||||
:created-at="dateFormat(item['created_at'])"
|
||||
:icon="item.icon"
|
||||
:model="model"
|
||||
:tokens="item['tokens']"/>
|
||||
:icon="item.icon"/>
|
||||
<chat-reply v-else-if="item.type==='reply'"
|
||||
:content="item.content"
|
||||
:created-at="dateFormat(item['created_at'])"
|
||||
:icon="item.icon"
|
||||
:org-content="item.orgContent"
|
||||
:tokens="item['tokens']"/>
|
||||
:org-content="item.orgContent"/>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="chat-box-wrapper">
|
||||
<van-sticky ref="bottomBarRef" :offset-bottom="0" position="bottom">
|
||||
|
||||
<van-cell-group inset>
|
||||
<van-field
|
||||
v-model="prompt"
|
||||
@@ -62,13 +60,13 @@
|
||||
clearable
|
||||
placeholder="输入你的问题"
|
||||
>
|
||||
<template #left-icon>
|
||||
<van-button round type="success" class="button-voice" @click="inputVoice">
|
||||
<el-icon>
|
||||
<Microphone/>
|
||||
</el-icon>
|
||||
</van-button>
|
||||
</template>
|
||||
<!-- <template #left-icon>-->
|
||||
<!-- <van-button round type="success" class="button-voice" @click="inputVoice">-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <Microphone/>-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- </van-button>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<template #button>
|
||||
<van-button size="small" type="primary" @click="sendMessage">发送</van-button>
|
||||
@@ -87,27 +85,47 @@
|
||||
|
||||
<button id="copy-link-btn" style="display: none;" :data-clipboard-text="url">复制链接地址</button>
|
||||
|
||||
<van-overlay :show="showMic" z-index="100">
|
||||
<div class="mic-wrapper">
|
||||
<div class="image">
|
||||
<van-image
|
||||
width="100"
|
||||
height="100"
|
||||
src="/images/mic.gif"
|
||||
/>
|
||||
</div>
|
||||
<van-button type="success" @click="stopVoice">说完了</van-button>
|
||||
</div>
|
||||
</van-overlay>
|
||||
<!-- <van-overlay :show="showMic" z-index="100">-->
|
||||
<!-- <div class="mic-wrapper">-->
|
||||
<!-- <div class="image">-->
|
||||
<!-- <van-image-->
|
||||
<!-- width="100"-->
|
||||
<!-- height="100"-->
|
||||
<!-- src="/images/mic.gif"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
<!-- <van-button type="success" @click="stopVoice">说完了</van-button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </van-overlay>-->
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showPicker" position="bottom" class="popup">
|
||||
<van-picker
|
||||
:columns="columns"
|
||||
title="选择模型和角色"
|
||||
@cancel="showPicker = false"
|
||||
@confirm="newChat"
|
||||
>
|
||||
<template #option="item">
|
||||
<div class="picker-option">
|
||||
<van-image
|
||||
v-if="item.icon"
|
||||
:src="item.icon"
|
||||
fit="cover"
|
||||
round
|
||||
/>
|
||||
<span>{{ item.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</van-picker>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue";
|
||||
import {showImagePreview, showNotify, showToast} from "vant";
|
||||
import {onBeforeRouteLeave, useRouter} from "vue-router";
|
||||
import {dateFormat, processContent, randString, renderInputText, UUID} from "@/utils/libs";
|
||||
import {getChatConfig} from "@/store/chat";
|
||||
import {processContent, randString, renderInputText, UUID} from "@/utils/libs";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import hl from "highlight.js";
|
||||
import 'highlight.js/styles/a11y-dark.css'
|
||||
@@ -116,26 +134,85 @@ import ChatReply from "@/components/mobile/ChatReply.vue";
|
||||
import {getSessionId, getUserToken} from "@/store/session";
|
||||
import {checkSession} from "@/action/session";
|
||||
import Clipboard from "clipboard";
|
||||
import {Microphone} from "@element-plus/icons-vue";
|
||||
import {showLoginDialog} from "@/utils/dialog";
|
||||
|
||||
const winHeight = ref(0)
|
||||
const navBarRef = ref(null)
|
||||
const bottomBarRef = ref(null)
|
||||
const router = useRouter()
|
||||
|
||||
const chatConfig = getChatConfig()
|
||||
const role = chatConfig.role
|
||||
const model = chatConfig.model
|
||||
const modelValue = chatConfig.modelValue
|
||||
const title = chatConfig.title
|
||||
const chatId = chatConfig.chatId
|
||||
const roles = ref([])
|
||||
const roleId = ref(parseInt(router.currentRoute.value.query["role_id"]))
|
||||
const role = ref({})
|
||||
const models = ref([])
|
||||
const modelId = ref(parseInt(router.currentRoute.value.query["model_id"]))
|
||||
const modelValue = ref("")
|
||||
const title = ref(router.currentRoute.value.query["title"])
|
||||
const chatId = ref(router.currentRoute.value.query["chat_id"])
|
||||
const loginUser = ref(null)
|
||||
const showMic = ref(false)
|
||||
// const showMic = ref(false)
|
||||
const showPicker = ref(false)
|
||||
const columns = ref([roles.value, models.value])
|
||||
|
||||
const url = location.protocol + '//' + location.host + '/mobile/chat/export?chat_id=' + chatId
|
||||
checkSession().then(user => {
|
||||
loginUser.value = user
|
||||
}).catch(() => {
|
||||
router.push('/login')
|
||||
})
|
||||
|
||||
if (chatId.value) {
|
||||
httpGet(`/api/chat/detail?chat_id=${chatId.value}`).then(res => {
|
||||
title.value = res.data.title
|
||||
modelId.value = res.data.model_id
|
||||
roleId.value = res.data.role_id
|
||||
}).catch(() => {
|
||||
})
|
||||
} else {
|
||||
title.value = "新建对话"
|
||||
}
|
||||
|
||||
// 加载模型
|
||||
httpGet('/api/model/list').then(res => {
|
||||
models.value = res.data
|
||||
if (!modelId.value) {
|
||||
modelId.value = models.value[0].id
|
||||
}
|
||||
for (let i = 0; i < models.value.length; i++) {
|
||||
models.value[i].text = models.value[i].name
|
||||
models.value[i].mValue = models.value[i].value
|
||||
models.value[i].value = models.value[i].id
|
||||
}
|
||||
modelValue.value = getModelValue(modelId.value)
|
||||
// 加载角色列表
|
||||
httpGet(`/api/role/list`).then((res) => {
|
||||
roles.value = res.data;
|
||||
if (!roleId.value) {
|
||||
roleId.value = roles.value[0]['id']
|
||||
}
|
||||
// build data for role picker
|
||||
for (let i = 0; i < roles.value.length; i++) {
|
||||
roles.value[i].text = roles.value[i].name
|
||||
roles.value[i].value = roles.value[i].id
|
||||
roles.value[i].helloMsg = roles.value[i].hello_msg
|
||||
}
|
||||
|
||||
role.value = getRoleById(roleId.value)
|
||||
columns.value = [roles.value, models.value]
|
||||
// 新建对话
|
||||
if (!chatId.value) {
|
||||
connect(chatId.value, roleId.value, modelId.value)
|
||||
}
|
||||
}).catch((e) => {
|
||||
showNotify({type: "danger", message: '获取聊天角色失败: ' + e.messages})
|
||||
})
|
||||
}).catch(e => {
|
||||
showNotify({type: "danger", message: "加载模型失败: " + e.message})
|
||||
})
|
||||
|
||||
const url = ref(location.protocol + '//' + location.host + '/mobile/chat/export?chat_id=' + chatId.value)
|
||||
|
||||
onMounted(() => {
|
||||
winHeight.value = document.body.offsetHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight
|
||||
winHeight.value = window.innerHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight - 70
|
||||
|
||||
const clipboard = new Clipboard(".content-mobile,.copy-code-mobile,#copy-link-btn");
|
||||
clipboard.on('success', (e) => {
|
||||
@@ -148,20 +225,29 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.value = null
|
||||
if (socket.value !== null) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const newChat = (item) => {
|
||||
showPicker.value = false
|
||||
const options = item.selectedOptions
|
||||
roleId.value = options[0].value
|
||||
modelId.value = options[1].value
|
||||
chatId.value = ""
|
||||
chatData.value = []
|
||||
role.value = getRoleById(roleId.value)
|
||||
title.value = "新建对话"
|
||||
connect(chatId.value, roleId.value, modelId.value)
|
||||
}
|
||||
|
||||
const chatData = ref([])
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const error = ref(false)
|
||||
|
||||
checkSession().then(user => {
|
||||
loginUser.value = user
|
||||
}).catch(() => {
|
||||
router.push('/login')
|
||||
})
|
||||
|
||||
const latexPlugin = require('markdown-it-latex2img')
|
||||
const mathjaxPlugin = require('markdown-it-mathjax')
|
||||
const md = require('markdown-it')({
|
||||
@@ -193,39 +279,41 @@ md.use(mathjaxPlugin)
|
||||
|
||||
|
||||
const onLoad = () => {
|
||||
httpGet('/api/chat/history?chat_id=' + chatId).then(res => {
|
||||
// 加载状态结束
|
||||
finished.value = true;
|
||||
const data = res.data
|
||||
if (data && data.length > 0) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].type === "prompt") {
|
||||
chatData.value.push(data[i]);
|
||||
continue;
|
||||
if (chatId.value) {
|
||||
checkSession().then(() => {
|
||||
httpGet('/api/chat/history?chat_id=' + chatId.value).then(res => {
|
||||
// 加载状态结束
|
||||
finished.value = true;
|
||||
const data = res.data
|
||||
if (data && data.length > 0) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].type === "prompt") {
|
||||
chatData.value.push(data[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
data[i].orgContent = data[i].content;
|
||||
data[i].content = md.render(processContent(data[i].content))
|
||||
chatData.value.push(data[i]);
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
hl.configure({ignoreUnescapedHTML: true})
|
||||
const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
|
||||
blocks.forEach((block) => {
|
||||
hl.highlightElement(block)
|
||||
})
|
||||
|
||||
scrollListBox()
|
||||
})
|
||||
}
|
||||
|
||||
data[i].orgContent = data[i].content;
|
||||
data[i].content = md.render(processContent(data[i].content))
|
||||
chatData.value.push(data[i]);
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
hl.configure({ignoreUnescapedHTML: true})
|
||||
const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
|
||||
blocks.forEach((block) => {
|
||||
hl.highlightElement(block)
|
||||
})
|
||||
|
||||
scrollListBox()
|
||||
connect(chatId.value, roleId.value, modelId.value);
|
||||
}).catch(() => {
|
||||
error.value = true
|
||||
})
|
||||
}
|
||||
|
||||
// 连接会话
|
||||
connect(chatId, role.id);
|
||||
}).catch(() => {
|
||||
error.value = true
|
||||
})
|
||||
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// 离开页面时主动关闭 websocket 连接,节省网络资源
|
||||
@@ -248,7 +336,7 @@ const socket = ref(null);
|
||||
const activelyClose = ref(false); // 主动关闭
|
||||
const canSend = ref(true);
|
||||
const heartbeatHandle = ref(null)
|
||||
const connect = function (chat_id, role_id) {
|
||||
const connect = function (chat_id, role_id, model_id) {
|
||||
let isNewChat = false;
|
||||
if (!chat_id) {
|
||||
isNewChat = true;
|
||||
@@ -278,7 +366,7 @@ const connect = function (chat_id, role_id) {
|
||||
}
|
||||
}
|
||||
|
||||
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${model}&token=${getUserToken()}`);
|
||||
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${model_id}&token=${getUserToken()}`);
|
||||
_socket.addEventListener('open', () => {
|
||||
loading.value = false
|
||||
previousText.value = '';
|
||||
@@ -289,9 +377,9 @@ const connect = function (chat_id, role_id) {
|
||||
chatData.value.push({
|
||||
type: "reply",
|
||||
id: randString(32),
|
||||
icon: role.icon,
|
||||
content: role.helloMsg,
|
||||
orgContent: role.helloMsg,
|
||||
icon: role.value.icon,
|
||||
content: role.value.hello_msg,
|
||||
orgContent: role.value.hello_msg,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -309,9 +397,12 @@ const connect = function (chat_id, role_id) {
|
||||
chatData.value.push({
|
||||
type: "reply",
|
||||
id: randString(32),
|
||||
icon: role.icon,
|
||||
icon: role.value.icon,
|
||||
content: ""
|
||||
});
|
||||
if (isNewChat) {
|
||||
title.value = previousText.value
|
||||
}
|
||||
} else if (data.type === 'end') { // 消息接收完毕
|
||||
enableInput()
|
||||
lineBuffer.value = ''; // 清空缓冲
|
||||
@@ -358,10 +449,9 @@ const connect = function (chat_id, role_id) {
|
||||
canSend.value = true;
|
||||
// 重连
|
||||
checkSession().then(() => {
|
||||
connect(chat_id, role_id)
|
||||
connect(chat_id, role_id, model_id)
|
||||
}).catch(() => {
|
||||
loading.value = true
|
||||
setTimeout(() => connect(chat_id, role_id), 3000)
|
||||
showLoginDialog(router)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -447,38 +537,56 @@ const shareChat = (option) => {
|
||||
showToast({message: "当前会话已经导出,请通过浏览器或者微信的自带分享功能分享给好友", duration: 5000})
|
||||
router.push({
|
||||
path: "/mobile/chat/export",
|
||||
query: {title: title, chat_id: chatId, role: role.name, model: modelValue}
|
||||
query: {title: title, chat_id: chatId, role: role.value.name, model: modelValue}
|
||||
})
|
||||
} else if (option.icon === "link") {
|
||||
document.getElementById('copy-link-btn').click();
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const recognition = new webkitSpeechRecognition() || SpeechRecognition();
|
||||
//recognition.lang = 'zh-CN' // 设置语音识别语言
|
||||
recognition.onresult = function (event) {
|
||||
prompt.value = event.results[0][0].transcript
|
||||
};
|
||||
|
||||
recognition.onerror = function (event) {
|
||||
showMic.value = false
|
||||
recognition.stop()
|
||||
showNotify({type: 'danger', message: '语音识别错误:' + event.error})
|
||||
};
|
||||
|
||||
recognition.onend = function () {
|
||||
console.log('语音识别结束');
|
||||
};
|
||||
const inputVoice = () => {
|
||||
showMic.value = true
|
||||
recognition.start();
|
||||
const getRoleById = function (rid) {
|
||||
for (let i = 0; i < roles.value.length; i++) {
|
||||
if (roles.value[i]['id'] === rid) {
|
||||
return roles.value[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const stopVoice = () => {
|
||||
showMic.value = false
|
||||
recognition.stop()
|
||||
const getModelValue = (model_id) => {
|
||||
for (let i = 0; i < models.value.length; i++) {
|
||||
if (models.value[i].id === model_id) {
|
||||
return models.value[i].mValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// // eslint-disable-next-line no-undef
|
||||
// const recognition = new webkitSpeechRecognition() || SpeechRecognition();
|
||||
// //recognition.lang = 'zh-CN' // 设置语音识别语言
|
||||
// recognition.onresult = function (event) {
|
||||
// prompt.value = event.results[0][0].transcript
|
||||
// };
|
||||
//
|
||||
// recognition.onerror = function (event) {
|
||||
// showMic.value = false
|
||||
// recognition.stop()
|
||||
// showNotify({type: 'danger', message: '语音识别错误:' + event.error})
|
||||
// };
|
||||
//
|
||||
// recognition.onend = function () {
|
||||
// console.log('语音识别结束');
|
||||
// };
|
||||
// const inputVoice = () => {
|
||||
// showMic.value = true
|
||||
// recognition.start();
|
||||
// }
|
||||
//
|
||||
// const stopVoice = () => {
|
||||
// showMic.value = false
|
||||
// recognition.stop()
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<van-config-provider :theme="getMobileTheme()">
|
||||
<van-config-provider :theme="theme">
|
||||
<div class="mobile-home">
|
||||
<router-view/>
|
||||
|
||||
<van-tabbar route v-model="active" @change="onChange">
|
||||
<van-tabbar-item to="/mobile/chat" name="home" icon="chat-o">对话</van-tabbar-item>
|
||||
<van-tabbar route v-model="active">
|
||||
<van-tabbar-item to="/mobile/index" name="home" icon="home-o">首页</van-tabbar-item>
|
||||
<van-tabbar-item to="/mobile/chat" name="chat" icon="chat-o">对话</van-tabbar-item>
|
||||
<van-tabbar-item to="/mobile/image" name="image" icon="photo-o">绘图</van-tabbar-item>
|
||||
<van-tabbar-item to="/mobile/img-wall" name="apps" icon="apps-o">广场</van-tabbar-item>
|
||||
<van-tabbar-item to="/mobile/profile" name="profile" icon="user-o">我的</van-tabbar-item>
|
||||
<van-tabbar-item to="/mobile/profile" name="profile" icon="user-o">我的
|
||||
</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
|
||||
</div>
|
||||
@@ -17,24 +18,23 @@
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {getMobileTheme} from "@/store/system";
|
||||
import {getMobileTheme, setMobileTheme} from "@/store/system";
|
||||
import {useRouter} from "vue-router";
|
||||
import {isMobile} from "@/utils/libs";
|
||||
import {checkSession} from "@/action/session";
|
||||
import bus from '@/store/eventbus'
|
||||
|
||||
const router = useRouter()
|
||||
checkSession().then(() => {
|
||||
if (!isMobile()) {
|
||||
router.replace('/chat')
|
||||
}
|
||||
}).catch(() => {
|
||||
router.push('/login')
|
||||
})
|
||||
if (!isMobile()) {
|
||||
router.replace('/')
|
||||
}
|
||||
|
||||
const active = ref('home')
|
||||
const onChange = (index) => {
|
||||
console.log(index)
|
||||
}
|
||||
const theme = ref(getMobileTheme())
|
||||
|
||||
bus.on('changeTheme', (value) => {
|
||||
theme.value = value
|
||||
setMobileTheme(theme.value)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -47,9 +47,7 @@ const onChange = (index) => {
|
||||
width 100%
|
||||
}
|
||||
|
||||
.content {
|
||||
padding 46px 10px 60px 10px
|
||||
}
|
||||
padding 0 10px
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,16 +57,6 @@ const onChange = (index) => {
|
||||
background #1c1c1e
|
||||
}
|
||||
|
||||
.van-toast--fail {
|
||||
background #fef0f0
|
||||
color #f56c6c
|
||||
}
|
||||
|
||||
.van-toast--success {
|
||||
background #D6FBCC
|
||||
color #07C160
|
||||
}
|
||||
|
||||
.van-nav-bar {
|
||||
position fixed
|
||||
width 100%
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="mobile-image container">
|
||||
<van-tabs v-model:active="activeName" class="my-tab" animated sticky>
|
||||
<van-tab title="MidJourney" name="mj">
|
||||
<van-tab title="MJ" name="mj">
|
||||
<image-mj/>
|
||||
</van-tab>
|
||||
<van-tab title="Stable-Diffusion" name="sd">
|
||||
<van-tab title="SD" name="sd">
|
||||
<image-sd/>
|
||||
</van-tab>
|
||||
<van-tab title="DALL-E" name="dall">
|
||||
<van-tab title="DALL" name="dall">
|
||||
<van-empty description="功能正在开发中"/>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import ImageMj from "@/views/mobile/ImageMj.vue";
|
||||
import ImageSd from "@/views/mobile/ImageSd.vue";
|
||||
import ImageMj from "@/views/mobile/pages/ImageMj.vue";
|
||||
import ImageSd from "@/views/mobile/pages/ImageSd.vue";
|
||||
|
||||
const activeName = ref("mj")
|
||||
</script>
|
||||
|
||||
235
web/src/views/mobile/Index.vue
Normal file
235
web/src/views/mobile/Index.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="index container">
|
||||
<h2 class="title">{{title}}</h2>
|
||||
<van-notice-bar left-icon="info-o" :scrollable="true">
|
||||
你有多少想象力,AI就有多大创造力。我辈之人,先干为敬,陪您先把 AI 用起来。
|
||||
</van-notice-bar>
|
||||
|
||||
<div class="content">
|
||||
<van-grid :column-num="3" :gutter="10" border>
|
||||
<van-grid-item @click="router.push('chat')">
|
||||
<template #icon>
|
||||
<i class="iconfont icon-chat"></i>
|
||||
</template>
|
||||
<template #text>
|
||||
<div class="text">AI 对话</div>
|
||||
|
||||
</template>
|
||||
</van-grid-item>
|
||||
|
||||
<van-grid-item @click="router.push('image')">
|
||||
<template #icon>
|
||||
<i class="iconfont icon-mj"></i>
|
||||
</template>
|
||||
<template #text>
|
||||
<div class="text">AI 绘画</div>
|
||||
</template>
|
||||
</van-grid-item>
|
||||
|
||||
<van-grid-item @click="router.push('imgWall')">
|
||||
<template #icon>
|
||||
<van-icon name="photo-o" />
|
||||
</template>
|
||||
<template #text>
|
||||
<div class="text">AI 画廊</div>
|
||||
</template>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
|
||||
<div class="app-list">
|
||||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="true"
|
||||
finished-text=""
|
||||
@load="fetchApps"
|
||||
>
|
||||
<van-cell v-for="item in apps" :key="item.id">
|
||||
<div>
|
||||
<div class="item">
|
||||
<div class="image">
|
||||
<van-image :src="item.icon" />
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="info-title">{{item.name}}</div>
|
||||
<div class="info-text">{{item.hello_msg}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn">
|
||||
<div v-if="hasRole(item.key)">
|
||||
<van-button size="small" type="success" @click="useRole(item.id)">使用</van-button>
|
||||
<van-button size="small" type="danger" @click="updateRole(item,'remove')">移除</van-button>
|
||||
</div>
|
||||
<van-button v-else size="small"
|
||||
style="--el-color-primary:#009999"
|
||||
@click="updateRole(item, 'add')">
|
||||
<van-icon name="add-o" />
|
||||
<span>添加应用</span>
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {arrayContains, removeArrayItem, showLoginDialog, substr} from "@/utils/libs";
|
||||
import {showNotify} from "vant";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const title = ref(process.env.VUE_APP_TITLE)
|
||||
const router = useRouter()
|
||||
const isLogin = ref(false)
|
||||
const apps = ref([])
|
||||
const loading = ref(false)
|
||||
const roles = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then((user) => {
|
||||
isLogin.value = true
|
||||
roles.value = user.chat_roles
|
||||
}).catch(() => {
|
||||
})
|
||||
fetchApps()
|
||||
})
|
||||
|
||||
const fetchApps = () => {
|
||||
httpGet("/api/role/list?all=true").then((res) => {
|
||||
const items = res.data
|
||||
// 处理 hello message
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].intro = substr(items[i].hello_msg, 80)
|
||||
}
|
||||
apps.value = items
|
||||
}).catch(e => {
|
||||
showNotify({type:"danger", message:"获取应用失败:" + e.message})
|
||||
})
|
||||
}
|
||||
|
||||
const updateRole = (row, opt) => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
|
||||
const title = ref("")
|
||||
if (opt === "add") {
|
||||
title.value = "添加应用"
|
||||
const exists = arrayContains(roles.value, row.key)
|
||||
if (exists) {
|
||||
return
|
||||
}
|
||||
roles.value.push(row.key)
|
||||
} else {
|
||||
title.value = "移除应用"
|
||||
const exists = arrayContains(roles.value, row.key)
|
||||
if (!exists) {
|
||||
return
|
||||
}
|
||||
roles.value = removeArrayItem(roles.value, row.key)
|
||||
}
|
||||
httpPost("/api/role/update", {keys: roles.value}).then(() => {
|
||||
ElMessage.success({message: title.value + "成功!", duration: 1000})
|
||||
}).catch(e => {
|
||||
ElMessage.error(title.value + "失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const hasRole = (roleKey) => {
|
||||
return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2)
|
||||
}
|
||||
|
||||
const useRole = (roleId) => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
router.push(`/mobile/chat/session?role_id=${roleId}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
.index {
|
||||
color var(--van-text-color)
|
||||
.title {
|
||||
display flex
|
||||
justify-content center
|
||||
|
||||
}
|
||||
--van-notice-bar-font-size: 16px
|
||||
|
||||
.content {
|
||||
padding 15px 0 60px 0
|
||||
.van-grid-item {
|
||||
.iconfont {
|
||||
font-size 20px
|
||||
}
|
||||
.text {
|
||||
display flex
|
||||
width 100%
|
||||
padding 10px
|
||||
justify-content center
|
||||
font-size 14px
|
||||
}
|
||||
}
|
||||
|
||||
.app-list {
|
||||
padding-top 10px
|
||||
|
||||
.item {
|
||||
display flex
|
||||
.image {
|
||||
width 80px
|
||||
height 80px
|
||||
min-width 80px
|
||||
border-radius 5px
|
||||
overflow hidden
|
||||
}
|
||||
|
||||
.info {
|
||||
text-align left
|
||||
padding 0 10px
|
||||
.info-title {
|
||||
color var(--van-text-color)
|
||||
font-size 1.25rem
|
||||
line-height 1.75rem
|
||||
letter-spacing: .025em;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
padding 5px 0
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
word-break: break-all;
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding 5px 0
|
||||
.van-button {
|
||||
margin-right 10px
|
||||
|
||||
.van-icon {
|
||||
margin-right 5px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,43 +1,55 @@
|
||||
<template>
|
||||
<div class="mobile-user-profile container">
|
||||
<van-nav-bar :title="title"/>
|
||||
|
||||
<div class="content">
|
||||
<van-form>
|
||||
<div class="avatar">
|
||||
<van-uploader v-model="fileList"
|
||||
reupload max-count="1"
|
||||
:deletable="false"
|
||||
:after-read="afterRead"/>
|
||||
</div>
|
||||
<van-cell-group inset v-model="form">
|
||||
<van-field
|
||||
v-model="form.username"
|
||||
name="账号"
|
||||
label="账号"
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
<van-field label="头像">
|
||||
<template #input>
|
||||
<van-uploader v-model="fileList"
|
||||
reupload max-count="1"
|
||||
:deletable="false"
|
||||
:after-read="afterRead"/>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
v-model="form.nickname"
|
||||
label="昵称"
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
|
||||
<van-field label="剩余算力">
|
||||
<van-field label="算力">
|
||||
<template #input>
|
||||
<van-tag type="primary">{{ form.power }}</van-tag>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field label="VIP到期时间" v-if="form.expired_time > 0">
|
||||
<van-field label="有效期" v-if="form.expired_time > 0">
|
||||
<template #input>
|
||||
<van-tag type="warning">{{ dateFormat(form.expired_time) }}</van-tag>
|
||||
{{ dateFormat(form.expired_time) }}
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
|
||||
<div class="modify-pass">
|
||||
<van-button round block type="primary" @click="showPasswordDialog = true">修改密码</van-button>
|
||||
<div class="opt" v-if="isLogin">
|
||||
<van-row :gutter="10">
|
||||
<van-col :span="8">
|
||||
<van-button round block @click="showPasswordDialog = true" size="small">修改密码</van-button>
|
||||
</van-col>
|
||||
<van-col :span="8">
|
||||
<van-button round block @click="logout" size="small">退出登录</van-button>
|
||||
</van-col>
|
||||
|
||||
<van-col :span="8">
|
||||
<van-button round block @click="showSettings = true" icon="setting" size="small">设置</van-button>
|
||||
</van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
|
||||
<div class="product-list">
|
||||
@@ -106,6 +118,34 @@
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
</van-dialog>
|
||||
|
||||
<van-action-sheet v-model:show="showSettings" title="用户设置">
|
||||
<div class="setting-content">
|
||||
<van-form>
|
||||
<van-cell-group inset>
|
||||
<van-field name="switch" label="暗黑主题">
|
||||
<template #input>
|
||||
<van-switch v-model="dark" @change="changeTheme"/>
|
||||
</template>
|
||||
</van-field>
|
||||
<!-- <van-field-->
|
||||
<!-- v-model="password"-->
|
||||
<!-- type="password"-->
|
||||
<!-- name="密码"-->
|
||||
<!-- label="密码"-->
|
||||
<!-- placeholder="密码"-->
|
||||
<!-- :rules="[{ required: true, message: '请填写密码' }]"-->
|
||||
<!-- />-->
|
||||
</van-cell-group>
|
||||
<!-- <div style="margin: 16px;">-->
|
||||
<!-- <van-button round block type="primary" native-type="submit">-->
|
||||
<!-- 提交-->
|
||||
<!-- </van-button>-->
|
||||
<!-- </div>-->
|
||||
</van-form>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -114,23 +154,24 @@ import {onMounted, ref} from "vue";
|
||||
import {showFailToast, showNotify, showSuccessToast} from "vant";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Compressor from 'compressorjs';
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {dateFormat, showLoginDialog} from "@/utils/libs";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
import {removeUserToken} from "@/store/session";
|
||||
import bus from '@/store/eventbus'
|
||||
import {getMobileTheme} from "@/store/system";
|
||||
|
||||
const title = ref('用户设置')
|
||||
const form = ref({
|
||||
username: '',
|
||||
nickname: '',
|
||||
mobile: '',
|
||||
username: 'GeekMaster',
|
||||
nickname: '极客学长@001',
|
||||
mobile: '1300000000',
|
||||
avatar: '',
|
||||
calls: 0,
|
||||
tokens: 0
|
||||
power: 0,
|
||||
})
|
||||
const fileList = ref([
|
||||
{
|
||||
url: '',
|
||||
url: '/images/user-info.png',
|
||||
message: '上传中...',
|
||||
}
|
||||
]);
|
||||
@@ -139,11 +180,14 @@ const products = ref([])
|
||||
const vipMonthPower = ref(0)
|
||||
const payWays = ref({})
|
||||
const router = useRouter()
|
||||
const loginUser = ref(null)
|
||||
const userId = ref(0)
|
||||
const isLogin = ref(false)
|
||||
const showSettings = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(user => {
|
||||
loginUser.value = user
|
||||
userId.value = user.id
|
||||
isLogin.value = true
|
||||
httpGet('/api/user/profile').then(res => {
|
||||
form.value = res.data
|
||||
fileList.value[0].url = form.value.avatar
|
||||
@@ -151,28 +195,27 @@ onMounted(() => {
|
||||
console.log(e.message)
|
||||
showFailToast('获取用户信息失败')
|
||||
});
|
||||
|
||||
// 获取产品列表
|
||||
httpGet("/api/product/list").then((res) => {
|
||||
products.value = res.data
|
||||
}).catch(e => {
|
||||
showFailToast("获取产品套餐失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
vipMonthPower.value = res.data['vip_month_power']
|
||||
}).catch(e => {
|
||||
showFailToast("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/payment/payWays").then(res => {
|
||||
payWays.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取支付方式失败:" + e.message)
|
||||
})
|
||||
|
||||
}).catch(() => {
|
||||
router.push("/login")
|
||||
|
||||
})
|
||||
|
||||
// 获取产品列表
|
||||
httpGet("/api/product/list").then((res) => {
|
||||
products.value = res.data
|
||||
}).catch(e => {
|
||||
showFailToast("获取产品套餐失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
vipMonthPower.value = res.data['vip_month_power']
|
||||
}).catch(e => {
|
||||
showFailToast("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/payment/payWays").then(res => {
|
||||
payWays.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取支付方式失败:" + e.message)
|
||||
})
|
||||
|
||||
})
|
||||
@@ -244,10 +287,14 @@ const updatePass = () => {
|
||||
}
|
||||
|
||||
const pay = (payWay, item) => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
|
||||
httpPost("/api/payment/mobile", {
|
||||
pay_way: payWay,
|
||||
product_id: item.id,
|
||||
user_id: loginUser.value.id
|
||||
user_id: userId.value
|
||||
}).then(res => {
|
||||
// console.log(res.data)
|
||||
location.href = res.data
|
||||
@@ -255,25 +302,55 @@ const pay = (payWay, item) => {
|
||||
showFailToast("生成支付订单失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const logout = function () {
|
||||
httpGet('/api/user/logout').then(() => {
|
||||
removeUserToken();
|
||||
router.push('/');
|
||||
}).catch(() => {
|
||||
showFailToast('注销失败!');
|
||||
})
|
||||
}
|
||||
|
||||
const dark = ref(getMobileTheme() === 'dark')
|
||||
|
||||
const changeTheme = () => {
|
||||
bus.emit('changeTheme', dark.value ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.mobile-user-profile {
|
||||
.content {
|
||||
padding-top 15px
|
||||
padding-bottom 60px
|
||||
|
||||
.avatar {
|
||||
display flex
|
||||
justify-content center
|
||||
|
||||
.van-image {
|
||||
border-radius 50%
|
||||
}
|
||||
}
|
||||
|
||||
.van-field__label {
|
||||
width 100px
|
||||
text-align right
|
||||
}
|
||||
|
||||
.modify-pass {
|
||||
.opt {
|
||||
padding 10px 15px
|
||||
}
|
||||
|
||||
.product-list {
|
||||
padding 0 15px
|
||||
|
||||
color var(--van-text-color)
|
||||
|
||||
.item {
|
||||
border 1px solid #e5e5e5
|
||||
border 1px solid var(--van-border-color)
|
||||
border-radius 10px
|
||||
margin-bottom 15px
|
||||
overflow hidden
|
||||
@@ -294,6 +371,10 @@ const pay = (payWay, item) => {
|
||||
}
|
||||
}
|
||||
|
||||
.van-cell__value {
|
||||
flex 2
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size 18px
|
||||
color #f56c6c
|
||||
@@ -301,5 +382,9 @@ const pay = (payWay, item) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-content {
|
||||
padding 16px
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -161,7 +161,7 @@
|
||||
</van-collapse>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<div class="text-line pt-6">
|
||||
<el-tag>绘图消耗{{ mjPower }}算力,U/V 操作消耗{{ mjActionPower }}算力,当前算力:{{ power }}</el-tag>
|
||||
</div>
|
||||
|
||||
@@ -270,26 +270,21 @@
|
||||
|
||||
</div>
|
||||
|
||||
<button style="display: none" class="copy-prompt" :data-clipboard-text="prompt" id="copy-btn">复制</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue";
|
||||
import {
|
||||
showConfirmDialog,
|
||||
showFailToast,
|
||||
showNotify,
|
||||
showToast,
|
||||
showDialog,
|
||||
showImagePreview,
|
||||
showSuccessToast
|
||||
} from "vant";
|
||||
import {showConfirmDialog, showFailToast, showImagePreview, showNotify, showSuccessToast, showToast} from "vant";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Compressor from "compressorjs";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
import {Delete} from "@element-plus/icons-vue";
|
||||
import {showLoginDialog} from "@/utils/libs";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
const activeColspan = ref([""])
|
||||
|
||||
@@ -335,23 +330,38 @@ const finishedJobs = ref([])
|
||||
const socket = ref(null)
|
||||
const power = ref(0)
|
||||
const activeName = ref("txt2img")
|
||||
const isLogin = ref(false)
|
||||
const prompt = ref('')
|
||||
const clipboard = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
clipboard.value = new Clipboard(".copy-prompt");
|
||||
clipboard.value.on('success', () => {
|
||||
showNotify({type: 'success', message: '复制成功', duration: 1000})
|
||||
})
|
||||
clipboard.value.on('error', () => {
|
||||
showNotify({type: 'danger', message: '复制失败', duration: 2000})
|
||||
})
|
||||
|
||||
checkSession().then(user => {
|
||||
power.value = user['power']
|
||||
userId.value = user.id
|
||||
|
||||
isLogin.value = true
|
||||
fetchRunningJobs()
|
||||
fetchFinishJobs(1)
|
||||
connect()
|
||||
|
||||
}).catch(() => {
|
||||
router.push('/login')
|
||||
// router.push('/login')
|
||||
});
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.value = null
|
||||
clipboard.value.destroy()
|
||||
if (socket.value !== null) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const mjPower = ref(1)
|
||||
@@ -564,6 +574,10 @@ const variation = (index, item) => {
|
||||
}
|
||||
|
||||
const generate = () => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
|
||||
if (params.value.prompt === '' && params.value.task_type === "image") {
|
||||
return showFailToast("请输入绘画提示词!")
|
||||
}
|
||||
@@ -610,11 +624,15 @@ const publishImage = (item, action) => {
|
||||
}
|
||||
|
||||
const showPrompt = (item) => {
|
||||
showDialog({
|
||||
prompt.value = item.prompt
|
||||
showConfirmDialog({
|
||||
title: "绘画提示词",
|
||||
message: item.prompt,
|
||||
confirmButtonText: "复制",
|
||||
cancelButtonText: "关闭",
|
||||
}).then(() => {
|
||||
// on close
|
||||
document.querySelector('#copy-btn').click()
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
<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="showTask(item)" circle>
|
||||
<el-button type="primary" @click="showPrompt(item)" circle>
|
||||
<i class="iconfont icon-prompt"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -208,7 +208,7 @@
|
||||
</van-list>
|
||||
|
||||
</div>
|
||||
|
||||
<button style="display: none" class="copy-prompt-sd" :data-clipboard-text="prompt" id="copy-btn-sd">复制</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -221,19 +221,19 @@ import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {
|
||||
showConfirmDialog, showDialog,
|
||||
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 showTaskDialog = ref(false)
|
||||
const item = ref({})
|
||||
const showLoginDialog = ref(false)
|
||||
const isLogin = ref(false)
|
||||
const activeColspan = ref([""])
|
||||
|
||||
@@ -338,15 +338,15 @@ const connect = () => {
|
||||
}
|
||||
|
||||
const clipboard = ref(null)
|
||||
const prompt = ref('')
|
||||
onMounted(() => {
|
||||
initData()
|
||||
clipboard.value = new Clipboard('.copy-prompt-sd');
|
||||
clipboard.value = new Clipboard(".copy-prompt-sd");
|
||||
clipboard.value.on('success', () => {
|
||||
showNotify({type: "success", message: "复制成功!"});
|
||||
showNotify({type: 'success', message: '复制成功', duration: 1000})
|
||||
})
|
||||
|
||||
clipboard.value.on('error', () => {
|
||||
showNotify({type: "danger", message: '复制失败!'});
|
||||
showNotify({type: 'danger', message: '复制失败', duration: 2000})
|
||||
})
|
||||
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
@@ -358,7 +358,10 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
socket.value = null
|
||||
if (socket.value !== null) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -429,17 +432,16 @@ const onLoad = () => {
|
||||
// 创建绘图任务
|
||||
const promptRef = ref(null)
|
||||
const generate = () => {
|
||||
if (!isLogin.value) {
|
||||
return showLoginDialog(router)
|
||||
}
|
||||
|
||||
if (params.value.prompt === '') {
|
||||
promptRef.value.focus()
|
||||
return showToast("请输入绘画提示词!")
|
||||
}
|
||||
|
||||
if (!isLogin.value) {
|
||||
showLoginDialog.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (params.value.seed === '') {
|
||||
if (!params.value.seed) {
|
||||
params.value.seed = -1
|
||||
}
|
||||
params.value.session_id = getSessionId()
|
||||
@@ -451,14 +453,17 @@ const generate = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const showTask = (row) => {
|
||||
item.value = row
|
||||
showTaskDialog.value = true
|
||||
}
|
||||
|
||||
const copyParams = (row) => {
|
||||
params.value = row.params
|
||||
showTaskDialog.value = false
|
||||
const showPrompt = (item) => {
|
||||
prompt.value = item.prompt
|
||||
showConfirmDialog({
|
||||
title: "绘画提示词",
|
||||
message: item.prompt,
|
||||
confirmButtonText: "复制",
|
||||
cancelButtonText: "关闭",
|
||||
}).then(() => {
|
||||
document.querySelector('#copy-btn-sd').click()
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const removeImage = (event, item) => {
|
||||
@@ -1,10 +1,8 @@
|
||||
<template>
|
||||
<div class="img-wall container">
|
||||
<van-nav-bar :title="title"/>
|
||||
|
||||
<div class="content">
|
||||
<van-tabs v-model:active="activeName">
|
||||
<van-tab title="MidJourney" name="mj">
|
||||
<van-tabs v-model:active="activeName" animated sticky>
|
||||
<van-tab title="MJ" name="mj">
|
||||
<van-list
|
||||
v-model:error="data['mj'].error"
|
||||
v-model:loading="data['mj'].loading"
|
||||
@@ -15,11 +13,17 @@
|
||||
style="height: 100%;width: 100%;"
|
||||
>
|
||||
<van-cell v-for="item in data['mj'].data" :key="item.id">
|
||||
<van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
|
||||
<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-tab title="StableDiffusion" name="sd">
|
||||
<van-tab title="SD" name="sd">
|
||||
<van-list
|
||||
v-model:error="data['sd'].error"
|
||||
v-model:loading="data['sd'].loading"
|
||||
@@ -29,25 +33,34 @@
|
||||
@load="onLoad"
|
||||
>
|
||||
<van-cell v-for="item in data['sd'].data" :key="item.id">
|
||||
<van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
|
||||
<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-tab title="DALLE3" name="dalle3">
|
||||
<van-tab title="DALL" name="dalle3">
|
||||
<van-empty description="功能正在开发中"/>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
|
||||
<button style="display: none" class="copy-prompt-wall" :data-clipboard-text="prompt" id="copy-btn-wall">复制
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {showDialog, showFailToast, showSuccessToast} from "vant";
|
||||
import {onMounted, onUnmounted, ref} from "vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {showConfirmDialog, showFailToast, showImagePreview, showNotify} from "vant";
|
||||
import Clipboard from "clipboard";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const title = ref('图片创作广场')
|
||||
const activeName = ref("mj")
|
||||
const data = ref({
|
||||
"mj": {
|
||||
@@ -56,7 +69,7 @@ const data = ref({
|
||||
error: false,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
url: "/api/mj/jobs",
|
||||
url: "/api/mj/imgWall",
|
||||
data: []
|
||||
},
|
||||
"sd": {
|
||||
@@ -65,7 +78,7 @@ const data = ref({
|
||||
error: false,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
url: "/api/sd/jobs",
|
||||
url: "/api/sd/imgWall",
|
||||
data: []
|
||||
},
|
||||
"dalle3": {
|
||||
@@ -74,11 +87,32 @@ const data = ref({
|
||||
error: false,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
url: "/api/dalle3/jobs",
|
||||
url: "/api/dalle3/imgWall",
|
||||
data: []
|
||||
}
|
||||
})
|
||||
|
||||
const prompt = ref('')
|
||||
const clipboard = ref(null)
|
||||
onMounted(() => {
|
||||
clipboard.value = new Clipboard(".copy-prompt-wall");
|
||||
clipboard.value.on('success', () => {
|
||||
showNotify({type: 'success', message: '复制成功', duration: 1000})
|
||||
})
|
||||
clipboard.value.on('error', () => {
|
||||
showNotify({type: 'danger', message: '复制失败', duration: 2000})
|
||||
})
|
||||
|
||||
|
||||
clipboard.value.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
})
|
||||
|
||||
const onLoad = () => {
|
||||
const d = data.value[activeName.value]
|
||||
httpGet(`${d.url}?status=1&page=${d.page}&page_size=${d.pageSize}&publish=true`).then(res => {
|
||||
@@ -109,24 +143,39 @@ const onLoad = () => {
|
||||
};
|
||||
|
||||
const showPrompt = (item) => {
|
||||
showDialog({
|
||||
prompt.value = item.prompt
|
||||
showConfirmDialog({
|
||||
title: "绘画提示词",
|
||||
message: item.prompt,
|
||||
confirmButtonText: "复制",
|
||||
cancelButtonText: "关闭",
|
||||
}).then(() => {
|
||||
// on close
|
||||
document.querySelector('#copy-btn-wall').click()
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const imageView = (item) => {
|
||||
showImagePreview([item['img_url']]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.img-wall {
|
||||
.content {
|
||||
padding-top 60px
|
||||
|
||||
.van-cell__value {
|
||||
min-height 80px
|
||||
|
||||
.van-image {
|
||||
width 100%
|
||||
}
|
||||
|
||||
.opt-box {
|
||||
position absolute
|
||||
right 0
|
||||
top 0
|
||||
padding 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user