fixed conflicts

This commit is contained in:
RockYang 2024-05-06 14:44:09 +08:00
commit 9a503ddc72
31 changed files with 289 additions and 172 deletions

View File

@ -1,5 +1,9 @@
# 更新日志 # 更新日志
## v4.0.6
* Bug修复修复PC端画廊页面的瀑布流组件样式错乱问题
## v4.0.5 ## v4.0.5
* 功能优化:已授权系统在后台显示授权信息 * 功能优化:已授权系统在后台显示授权信息

View File

@ -1,5 +1,5 @@
SHELL=/usr/bin/env bash SHELL=/usr/bin/env bash
NAME := chatgpt-plus NAME := geekai
all: amd64 arm64 all: amd64 arm64
amd64: amd64:

View File

@ -207,10 +207,13 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
func needLogin(c *gin.Context) bool { func needLogin(c *gin.Context) bool {
if c.Request.URL.Path == "/api/user/login" || if c.Request.URL.Path == "/api/user/login" ||
c.Request.URL.Path == "/api/user/logout" ||
c.Request.URL.Path == "/api/user/resetPass" || c.Request.URL.Path == "/api/user/resetPass" ||
c.Request.URL.Path == "/api/admin/login" || c.Request.URL.Path == "/api/admin/login" ||
c.Request.URL.Path == "/api/admin/logout" ||
c.Request.URL.Path == "/api/admin/login/captcha" || c.Request.URL.Path == "/api/admin/login/captcha" ||
c.Request.URL.Path == "/api/user/register" || c.Request.URL.Path == "/api/user/register" ||
c.Request.URL.Path == "/api/user/session" ||
c.Request.URL.Path == "/api/chat/history" || c.Request.URL.Path == "/api/chat/history" ||
c.Request.URL.Path == "/api/chat/detail" || c.Request.URL.Path == "/api/chat/detail" ||
c.Request.URL.Path == "/api/chat/list" || c.Request.URL.Path == "/api/chat/list" ||

View File

@ -144,6 +144,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
for { for {
_, msg, err := client.Receive() _, msg, err := client.Receive()
if err != nil { if err != nil {
logger.Debugf("close connection: %s", client.Conn.RemoteAddr())
client.Close() client.Close()
h.App.ChatClients.Delete(sessionId) h.App.ChatClients.Delete(sessionId)
h.App.ChatSession.Delete(sessionId) h.App.ChatSession.Delete(sessionId)

View File

@ -188,6 +188,11 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
return return
} }
if user.Power < h.App.SysConfig.DallPower {
resp.ERROR(c, "创建 DALL-E 绘图任务失败,算力不足")
return
}
// create dall task // create dall task
prompt := utils.InterfaceToString(params["prompt"]) prompt := utils.InterfaceToString(params["prompt"])
job := model.DallJob{ job := model.DallJob{

View File

@ -13,21 +13,22 @@ import (
"geekai/core" "geekai/core"
"geekai/core/types" "geekai/core/types"
"geekai/store" "geekai/store"
"github.com/imroc/req/v3"
"github.com/shirou/gopsutil/host"
"strings" "strings"
"time" "time"
"github.com/imroc/req/v3"
"github.com/shirou/gopsutil/host"
) )
type LicenseService struct { type LicenseService struct {
config types.ApiConfig config types.ApiConfig
levelDB *store.LevelDB levelDB *store.LevelDB
license types.License license *types.License
urlWhiteList []string urlWhiteList []string
machineId string machineId string
} }
func NewLicenseService(server *core.AppServer, levelDB *store.LevelDB) * LicenseService { func NewLicenseService(server *core.AppServer, levelDB *store.LevelDB) *LicenseService {
var license types.License var license types.License
var machineId string var machineId string
_ = levelDB.Get(types.LicenseKey, &license) _ = levelDB.Get(types.LicenseKey, &license)
@ -35,18 +36,20 @@ func NewLicenseService(server *core.AppServer, levelDB *store.LevelDB) * License
if err == nil { if err == nil {
machineId = info.HostID machineId = info.HostID
} }
logger.Infof("License: %+v", license)
return &LicenseService{ return &LicenseService{
config: server.Config.ApiConfig, config: server.Config.ApiConfig,
levelDB: levelDB, levelDB: levelDB,
license: license, license: &license,
machineId: machineId, machineId: machineId,
} }
} }
type License struct { type License struct {
Name string `json:"name"` Name string `json:"name"`
Value string `json:"license"` License string `json:"license"`
Mid string `json:"mid"` Mid string `json:"mid"`
ActiveAt int64 `json:"active_at"`
ExpiredAt int64 `json:"expired_at"` ExpiredAt int64 `json:"expired_at"`
UserNum int `json:"user_num"` UserNum int `json:"user_num"`
} }
@ -67,14 +70,14 @@ func (s *LicenseService) ActiveLicense(license string, machineId string) error {
} }
if response.IsErrorState() { if response.IsErrorState() {
return fmt.Errorf( "发送激活请求失败:%v", response.Status) return fmt.Errorf("发送激活请求失败:%v", response.Status)
} }
if res.Code != types.Success { if res.Code != types.Success {
return fmt.Errorf("激活失败:%v", res.Message) return fmt.Errorf("激活失败:%v", res.Message)
} }
s.license = types.License{ s.license = &types.License{
Key: license, Key: license,
MachineId: machineId, MachineId: machineId,
UserNum: res.Data.UserNum, UserNum: res.Data.UserNum,
@ -91,50 +94,81 @@ func (s *LicenseService) ActiveLicense(license string, machineId string) error {
// SyncLicense 定期同步 License // SyncLicense 定期同步 License
func (s *LicenseService) SyncLicense() { func (s *LicenseService) SyncLicense() {
go func() { go func() {
retryCounter := 0
for { for {
var res struct { license, err := s.fetchLicense()
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data struct {
License License `json:"license"`
Urls []string `json:"urls"`
}
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/check")
response, err := req.C().R().
SetBody(map[string]string{"license": s.license.Key, "machine_id": s.machineId}).
SetSuccessResult(&res).Post(apiURL)
if err != nil { if err != nil {
logger.Errorf("发送激活请求失败: %v", err) retryCounter++
goto next if retryCounter < 5 {
} logger.Error(err)
if response.IsErrorState() { }
logger.Errorf("激活失败:%v", response.Status)
goto next
}
if res.Code != types.Success {
logger.Errorf("激活失败:%v", res.Message)
s.license.IsActive = false s.license.IsActive = false
goto next } else {
s.license = license
} }
s.license = types.License{ urls, err := s.fetchUrlWhiteList()
Key: res.Data.License.Value, if err == nil {
MachineId: res.Data.License.Mid, s.urlWhiteList = urls
UserNum: res.Data.License.UserNum,
ExpiredAt: res.Data.License.ExpiredAt,
IsActive: true,
} }
s.urlWhiteList = res.Data.Urls
logger.Debugf("同步 License 成功:%v\n%v", s.license, s.urlWhiteList)
next:
time.Sleep(time.Second * 10) time.Sleep(time.Second * 10)
} }
}() }()
} }
func (s *LicenseService) fetchLicense() (*types.License, error) {
var res struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data License `json:"data"`
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/check")
response, err := req.C().R().
SetBody(map[string]string{"license": s.license.Key, "machine_id": s.machineId}).
SetSuccessResult(&res).Post(apiURL)
if err != nil {
return nil, fmt.Errorf("发送激活请求失败: %v", err)
}
if response.IsErrorState() {
return nil, fmt.Errorf("激活失败:%v", response.Status)
}
if res.Code != types.Success {
return nil, fmt.Errorf("激活失败:%v", res.Message)
}
return &types.License{
Key: res.Data.License,
MachineId: res.Data.Mid,
UserNum: res.Data.UserNum,
ExpiredAt: res.Data.ExpiredAt,
IsActive: true,
}, nil
}
func (s *LicenseService) fetchUrlWhiteList() ([]string, error) {
var res struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data []string `json:"data"`
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/urls")
response, err := req.C().R().SetSuccessResult(&res).Get(apiURL)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %v", err)
}
if response.IsErrorState() {
return nil, fmt.Errorf("发送请求失败:%v", response.Status)
}
if res.Code != types.Success {
return nil, fmt.Errorf("获取白名单失败:%v", res.Message)
}
return res.Data, nil
}
// GetLicense 获取许可信息 // GetLicense 获取许可信息
func (s *LicenseService) GetLicense() types.License { func (s *LicenseService) GetLicense() *types.License {
return s.license return s.license
} }
@ -158,4 +192,4 @@ func (s *LicenseService) IsValidApiURL(uri string) error {
} }
} }
return fmt.Errorf("当前 API 地址 %s 不在白名单列表当中。", uri) return fmt.Errorf("当前 API 地址 %s 不在白名单列表当中。", uri)
} }

View File

@ -14,15 +14,15 @@ npm run build
cd ../build cd ../build
# remove docker image if exists # remove docker image if exists
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version-$arch docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version-$arch
# build docker image for chatgpt-plus-go # build docker image for Geek-AI API
docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version-$arch -f dockerfile-api-go ../ docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version-$arch -f dockerfile-api-go ../
# build docker image for chatgpt-plus-vue # build docker image for Geek-AI-web
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version-$arch docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch
docker build --platform linux/amd64 -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version-$arch -f dockerfile-vue ../ docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch -f dockerfile-vue ../
if [ "$3" = "push" ];then if [ "$3" = "push" ];then
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version-$arch docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version-$arch
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version-$arch docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch
fi fi

View File

@ -4,9 +4,9 @@ FROM registry.cn-hangzhou.aliyuncs.com/geekmaster/alpine:3.18.2
MAINTAINER yangjian<yangjian102621@163.com> MAINTAINER yangjian<yangjian102621@163.com>
WORKDIR /var/www/app WORKDIR /var/www/app
COPY ./api/bin/chatgpt-plus-linux /var/www/app COPY ./api/bin/geekai-linux /var/www/app
EXPOSE 5678 EXPOSE 5678
# 容器启动时执行的命令 # 容器启动时执行的命令
CMD ["./chatgpt-plus-linux"] CMD ["./geekai-linux"]

View File

@ -66,4 +66,14 @@ html, body {
white-space: nowrap; white-space: nowrap;
} }
.van-toast--fail {
background #fef0f0
color #f56c6c
}
.van-toast--success {
background #D6FBCC
color #07C160
}
</style> </style>

View File

@ -9,7 +9,7 @@
.chat-list-wrapper { .chat-list-wrapper {
padding 10px 0 10px 0 padding 10px 0 10px 0
background #F5F5F5; background var(--van-background);
overflow hidden overflow hidden
@ -81,7 +81,7 @@
.van-theme-dark { .van-theme-dark {
.mobile-chat { .mobile-chat {
.message-list-box { .chat-list-wrapper {
background #232425; background #232425;
} }
} }

View File

@ -74,6 +74,7 @@
} }
color var(--van-text-color)
.pt-6 { .pt-6 {
padding 15px 10px padding 15px 10px
} }

View File

@ -80,6 +80,8 @@
} }
color var(--van-text-color)
.pt-6 { .pt-6 {
padding 15px 10px padding 15px 10px
} }

View File

@ -38,11 +38,11 @@
import {ref} from "vue"; import {ref} from "vue";
import lodash from 'lodash' import lodash from 'lodash'
import {validateEmail, validateMobile} from "@/utils/validate"; import {validateEmail, validateMobile} from "@/utils/validate";
import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import CaptchaPlus from "@/components/CaptchaPlus.vue"; import CaptchaPlus from "@/components/CaptchaPlus.vue";
import SlideCaptcha from "@/components/SlideCaptcha.vue"; import SlideCaptcha from "@/components/SlideCaptcha.vue";
import {isMobile} from "@/utils/libs"; import {isMobile} from "@/utils/libs";
import {showMessageError, showMessageOK} from "@/utils/dialog";
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const props = defineProps({ const props = defineProps({
@ -66,13 +66,13 @@ const handleRequestCaptCode = () => {
thumbBase64.value = data.thumb thumbBase64.value = data.thumb
captKey.value = data.key captKey.value = data.key
}).catch(e => { }).catch(e => {
ElMessage.error('获取人机验证数据失败:' + e.message) showMessageError('获取人机验证数据失败:' + e.message)
}) })
} }
const handleConfirm = (dots) => { const handleConfirm = (dots) => {
if (lodash.size(dots) <= 0) { if (lodash.size(dots) <= 0) {
return ElMessage.error('请进行人机验证再操作') return showMessageError('请进行人机验证再操作')
} }
let dotArr = [] let dotArr = []
@ -88,14 +88,14 @@ const handleConfirm = (dots) => {
showCaptcha.value = false showCaptcha.value = false
sendMsg() sendMsg()
}).catch(() => { }).catch(() => {
ElMessage.error('人机验证失败') showMessageError('人机验证失败')
handleRequestCaptCode() handleRequestCaptCode()
}) })
} }
const loadCaptcha = () => { const loadCaptcha = () => {
if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) { if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) {
return ElMessage.error("请输入合法的手机号/邮箱地址") return showMessageError("请输入合法的手机号/邮箱地址")
} }
showCaptcha.value = true showCaptcha.value = true
@ -114,7 +114,7 @@ const sendMsg = () => {
canSend.value = false canSend.value = false
httpPost('/api/sms/code', {receiver: props.receiver, key: captKey.value, dots: dots.value}).then(() => { httpPost('/api/sms/code', {receiver: props.receiver, key: captKey.value, dots: dots.value}).then(() => {
ElMessage.success('验证码发送成功') showMessageOK('验证码发送成功')
let time = 120 let time = 120
btnText.value = time btnText.value = time
const handler = setInterval(() => { const handler = setInterval(() => {
@ -129,7 +129,7 @@ const sendMsg = () => {
}, 1000) }, 1000)
}).catch(e => { }).catch(e => {
canSend.value = true canSend.value = true
ElMessage.error('验证码发送失败:' + e.message) showMessageError('验证码发送失败:' + e.message)
}) })
} }
@ -145,7 +145,7 @@ const getSlideCaptcha = () => {
bgImg.value = res.data.bgImg bgImg.value = res.data.bgImg
captKey.value = res.data.key captKey.value = res.data.key
}).catch(e => { }).catch(e => {
ElMessage.error('获取人机验证数据失败:' + e.message) showMessageError('获取人机验证数据失败:' + e.message)
}) })
} }

43
web/src/utils/dialog.js Normal file
View 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)
}
}

View File

@ -6,7 +6,7 @@
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import axios from 'axios' 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.timeout = 180000
axios.defaults.baseURL = process.env.VUE_APP_API_HOST axios.defaults.baseURL = process.env.VUE_APP_API_HOST
@ -29,9 +29,14 @@ axios.interceptors.response.use(
let data = response.data; let data = response.data;
if (data.code === 0) { if (data.code === 0) {
return response return response
} else { } else if (data.code === 400) {
return Promise.reject(response.data) if (response.request.responseURL.indexOf("/api/admin") !== -1) {
removeAdminToken()
} else {
removeUserToken()
}
} }
return Promise.reject(response.data)
}, error => { }, error => {
return Promise.reject(error) return Promise.reject(error)
}) })

View File

@ -13,7 +13,7 @@
<span class="name">{{ scope.item.name }}</span> <span class="name">{{ scope.item.name }}</span>
<div class="opt"> <div class="opt">
<div v-if="hasRole(scope.item.key)"> <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> <el-button size="small" type="danger" @click="updateRole(scope.item,'remove')">移除</el-button>
</div> </div>
<el-button v-else size="small" <el-button v-else size="small"
@ -110,8 +110,8 @@ const hasRole = (roleKey) => {
} }
const router = useRouter() const router = useRouter()
const useRole = (roleId) => { const useRole = (role) => {
router.push({name: "chat", params: {role_id: roleId}}) router.push(`/chat?role_id=${role.id}`)
} }
</script> </script>

View File

@ -330,7 +330,10 @@ onMounted(() => {
}); });
onUnmounted(() => { onUnmounted(() => {
socket.value = null if (socket.value !== null) {
socket.value.close()
socket.value = null
}
}) })
// //
@ -355,8 +358,8 @@ const initData = () => {
// //
httpGet(`/api/role/list`).then((res) => { httpGet(`/api/role/list`).then((res) => {
roles.value = res.data; roles.value = res.data;
if (router.currentRoute.value.params.role_id) { if (router.currentRoute.value.query.role_id) {
roleId.value = parseInt(router.currentRoute.value.params["role_id"]) roleId.value = parseInt(router.currentRoute.value.query.role_id)
} else { } else {
roleId.value = roles.value[0]['id'] roleId.value = roles.value[0]['id']
} }

View File

@ -105,7 +105,7 @@
</div> </div>
<h2>创作记录</h2> <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"> <div v-if="finishedJobs.length > 0">
<ItemList :items="finishedJobs" :width="240" :gap="16"> <ItemList :items="finishedJobs" :width="240" :gap="16">
<template #default="scope"> <template #default="scope">
@ -272,7 +272,6 @@ const initData = () => {
fetchFinishJobs(1) fetchFinishJobs(1)
connect() connect()
}).catch(() => { }).catch(() => {
loading.value = false
}); });
} }
@ -341,6 +340,9 @@ const connect = () => {
} }
const fetchRunningJobs = () => { const fetchRunningJobs = () => {
if (!isLogin.value) {
return
}
// //
httpGet(`/api/dall/jobs?status=0`).then(res => { httpGet(`/api/dall/jobs?status=0`).then(res => {
const jobs = res.data const jobs = res.data
@ -367,10 +369,11 @@ const fetchRunningJobs = () => {
const page = ref(1) const page = ref(1)
const pageSize = ref(15) const pageSize = ref(15)
const isOver = ref(false) const isOver = ref(false)
const loading = ref(false)
// //
const fetchFinishJobs = (page) => { const fetchFinishJobs = (page) => {
loading.value = true if (!isLogin.value) {
return
}
httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => { httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
if (res.data.length < pageSize.value) { if (res.data.length < pageSize.value) {
isOver.value = true isOver.value = true
@ -380,9 +383,7 @@ const fetchFinishJobs = (page) => {
} else { } else {
finishedJobs.value = finishedJobs.value.concat(res.data) finishedJobs.value = finishedJobs.value.concat(res.data)
} }
loading.value = false
}).catch(e => { }).catch(e => {
loading.value = false
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)
}) })
} }

View File

@ -493,7 +493,7 @@
</div> </div>
<h2>创作记录</h2> <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"> <div v-if="finishedJobs.length > 0">
<ItemList :items="finishedJobs" :width="240" :gap="16"> <ItemList :items="finishedJobs" :width="240" :gap="16">
<template #default="scope"> <template #default="scope">
@ -593,7 +593,7 @@
</template> </template>
<script setup> <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 {ChromeFilled, Delete, DocumentCopy, InfoFilled, Picture, Plus, UploadFilled} from "@element-plus/icons-vue";
import Compressor from "compressorjs"; import Compressor from "compressorjs";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
@ -761,7 +761,10 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
clipboard.value.destroy() clipboard.value.destroy()
socket.value = null if (socket.value !== null) {
socket.value.close()
socket.value = null
}
}) })
// //
@ -779,10 +782,6 @@ const initData = () => {
}); });
} }
onUnmounted(() => {
clipboard.value.destroy()
})
const mjPower = ref(1) const mjPower = ref(1)
const mjActionPower = ref(1) const mjActionPower = ref(1)
httpGet("/api/config/get?key=system").then(res => { httpGet("/api/config/get?key=system").then(res => {
@ -794,6 +793,10 @@ httpGet("/api/config/get?key=system").then(res => {
// //
const fetchRunningJobs = () => { const fetchRunningJobs = () => {
if (!isLogin.value) {
return
}
httpGet(`/api/mj/jobs?status=0`).then(res => { httpGet(`/api/mj/jobs?status=0`).then(res => {
const jobs = res.data const jobs = res.data
const _jobs = [] const _jobs = []
@ -833,9 +836,11 @@ const handleScrollEnd = () => {
const page = ref(1) const page = ref(1)
const pageSize = ref(15) const pageSize = ref(15)
const isOver = ref(false) const isOver = ref(false)
const loading = ref(false)
const fetchFinishJobs = (page) => { const fetchFinishJobs = (page) => {
loading.value = true if (!isLogin.value) {
return
}
// //
httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => { httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
const jobs = res.data const jobs = res.data
@ -862,9 +867,7 @@ const fetchFinishJobs = (page) => {
} else { } else {
finishedJobs.value = finishedJobs.value.concat(jobs) finishedJobs.value = finishedJobs.value.concat(jobs)
} }
nextTick(() => loading.value = false)
}).catch(e => { }).catch(e => {
loading.value = false
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)
}) })
} }

View File

@ -315,7 +315,7 @@
<el-empty :image-size="100" v-else/> <el-empty :image-size="100" v-else/>
</div> </div>
<h2>创作记录</h2> <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"> <div v-if="finishedJobs.length > 0">
<ItemList :items="finishedJobs" :width="240" :gap="16"> <ItemList :items="finishedJobs" :width="240" :gap="16">
<template #default="scope"> <template #default="scope">
@ -616,7 +616,10 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
clipboard.value.destroy() clipboard.value.destroy()
socket.value = null if (socket.value !== null) {
socket.value.close()
socket.value = null
}
}) })
@ -629,11 +632,14 @@ const initData = () => {
fetchFinishJobs() fetchFinishJobs()
connect() connect()
}).catch(() => { }).catch(() => {
loading.value = false
}); });
} }
const fetchRunningJobs = () => { const fetchRunningJobs = () => {
if (!isLogin.value) {
return
}
// //
httpGet(`/api/sd/jobs?status=0`).then(res => { httpGet(`/api/sd/jobs?status=0`).then(res => {
const jobs = res.data const jobs = res.data
@ -668,10 +674,11 @@ const handleScrollEnd = () => {
const page = ref(1) const page = ref(1)
const pageSize = ref(15) const pageSize = ref(15)
const isOver = ref(false) const isOver = ref(false)
const loading = ref(false)
// //
const fetchFinishJobs = (page) => { const fetchFinishJobs = (page) => {
loading.value = true if (!isLogin.value) {
return
}
httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => { httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
if (res.data.length < pageSize.value) { if (res.data.length < pageSize.value) {
isOver.value = true isOver.value = true
@ -681,9 +688,7 @@ const fetchFinishJobs = (page) => {
} else { } else {
finishedJobs.value = finishedJobs.value.concat(res.data) finishedJobs.value = finishedJobs.value.concat(res.data)
} }
loading.value = false
}).catch(e => { }).catch(e => {
loading.value = false
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)
}) })
} }

View File

@ -5,9 +5,9 @@
<h2>AI 绘画作品墙</h2> <h2>AI 绘画作品墙</h2>
<div class="settings"> <div class="settings">
<el-radio-group v-model="imgType" @change="changeImgType"> <el-radio-group v-model="imgType" @change="changeImgType">
<el-radio label="mj" size="large">MidJourney</el-radio> <el-radio value="mj" size="large">MidJourney</el-radio>
<el-radio label="sd" size="large">Stable Diffusion</el-radio> <el-radio value="sd" size="large">Stable Diffusion</el-radio>
<el-radio label="dall" size="large">DALL-E</el-radio> <el-radio value="dall" size="large">DALL-E</el-radio>
</el-radio-group> </el-radio-group>
</div> </div>
</div> </div>
@ -72,7 +72,7 @@
</template> </template>
</v3-waterfall> </v3-waterfall>
<v3-waterfall v-if="imgType === 'dall'" <v3-waterfall v-else-if="imgType === 'dall'"
id="waterfall" id="waterfall"
:list="data['dall']" :list="data['dall']"
srcKey="img_thumb" srcKey="img_thumb"
@ -338,7 +338,6 @@ const getNext = () => {
loading.value = true loading.value = true
page.value = page.value + 1 page.value = page.value + 1
let url = "" let url = ""
console.log(imgType.value)
switch (imgType.value) { switch (imgType.value) {
case "mj": case "mj":
url = "/api/mj/imgWall" url = "/api/mj/imgWall"

View File

@ -56,13 +56,13 @@
import {ref} from "vue"; import {ref} from "vue";
import {Lock, UserFilled} from "@element-plus/icons-vue"; import {Lock, UserFilled} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import FooterBar from "@/components/FooterBar.vue"; import FooterBar from "@/components/FooterBar.vue";
import {isMobile} from "@/utils/libs"; import {isMobile} from "@/utils/libs";
import {checkSession} from "@/action/session"; import {checkSession} from "@/action/session";
import {setUserToken} from "@/store/session"; import {setUserToken} from "@/store/session";
import ResetPass from "@/components/ResetPass.vue"; import ResetPass from "@/components/ResetPass.vue";
import {showMessageError} from "@/utils/dialog";
const router = useRouter(); const router = useRouter();
const title = ref('Geek-AI'); const title = ref('Geek-AI');
@ -76,7 +76,7 @@ httpGet("/api/config/get?key=system").then(res => {
logo.value = res.data.logo logo.value = res.data.logo
title.value = res.data.title title.value = res.data.title
}).catch(e => { }).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message) showMessageError("获取系统配置失败:" + e.message)
}) })
@ -97,10 +97,10 @@ const handleKeyup = (e) => {
const login = function () { const login = function () {
if (username.value.trim() === '') { if (username.value.trim() === '') {
return ElMessage.error("请输入用户民") return showMessageError("请输入用户民")
} }
if (password.value.trim() === '') { if (password.value.trim() === '') {
return ElMessage.error('请输入密码'); return showMessageError('请输入密码');
} }
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => { httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
@ -112,11 +112,10 @@ const login = function () {
} }
}).catch((e) => { }).catch((e) => {
ElMessage.error('登录失败,' + e.message) showMessageError('登录失败,' + e.message)
}) })
} }
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>

View File

@ -264,9 +264,10 @@ const connect = (userId) => {
_socket.addEventListener('close', () => { _socket.addEventListener('close', () => {
loading.value = false loading.value = false
if (socket.value !== null) { checkSession().then(() => {
connect(userId) connect(userId)
} }).catch(() => {
})
}); });
} }

View File

@ -180,6 +180,7 @@ import SendMsg from "@/components/SendMsg.vue";
import {arrayContains} from "@/utils/libs"; import {arrayContains} from "@/utils/libs";
import {setUserToken} from "@/store/session"; import {setUserToken} from "@/store/session";
import {validateEmail, validateMobile} from "@/utils/validate"; import {validateEmail, validateMobile} from "@/utils/validate";
import {showMessageError, showMessageOK} from "@/utils/dialog";
const router = useRouter(); const router = useRouter();
const title = ref('Geek-AI 用户注册'); const title = ref('Geek-AI 用户注册');
@ -227,37 +228,37 @@ httpGet("/api/config/get?key=system").then(res => {
// //
const submitRegister = () => { const submitRegister = () => {
if (data.value.username === '') { if (data.value.username === '') {
return ElMessage.error('请输入用户名'); return showMessageError('请输入用户名');
} }
if (activeName.value === 'mobile' && !validateMobile(data.value.username)) { if (activeName.value === 'mobile' && !validateMobile(data.value.username)) {
return ElMessage.error('请输入合法的手机号'); return showMessageError('请输入合法的手机号');
} }
if (activeName.value === 'email' && !validateEmail(data.value.username)) { if (activeName.value === 'email' && !validateEmail(data.value.username)) {
return ElMessage.error('请输入合法的邮箱地址'); return showMessageError('请输入合法的邮箱地址');
} }
if (data.value.password.length < 8) { if (data.value.password.length < 8) {
return ElMessage.error('密码的长度为8-16个字符'); return showMessageError('密码的长度为8-16个字符');
} }
if (data.value.repass !== data.value.password) { if (data.value.repass !== data.value.password) {
return ElMessage.error('两次输入密码不一致'); return showMessageError('两次输入密码不一致');
} }
if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') { if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') {
return ElMessage.error('请输入验证码'); return showMessageError('请输入验证码');
} }
data.value.reg_way = activeName.value data.value.reg_way = activeName.value
httpPost('/api/user/register', data.value).then((res) => { httpPost('/api/user/register', data.value).then((res) => {
setUserToken(res.data) setUserToken(res.data)
ElMessage.success({ showMessageOK({
"message": "注册成功,即将跳转到对话主界面...", "message": "注册成功,即将跳转到对话主界面...",
onClose: () => router.push("/chat"), onClose: () => router.push("/chat"),
duration: 1000 duration: 1000
}) })
}).catch((e) => { }).catch((e) => {
ElMessage.error('注册失败,' + e.message) showMessageError('注册失败,' + e.message)
}) })
} }

View File

@ -134,6 +134,7 @@ import ChatReply from "@/components/mobile/ChatReply.vue";
import {getSessionId, getUserToken} from "@/store/session"; import {getSessionId, getUserToken} from "@/store/session";
import {checkSession} from "@/action/session"; import {checkSession} from "@/action/session";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import {showLoginDialog} from "@/utils/dialog";
const winHeight = ref(0) const winHeight = ref(0)
const navBarRef = ref(null) const navBarRef = ref(null)
@ -178,6 +179,7 @@ httpGet('/api/model/list').then(res => {
} }
for (let i = 0; i < models.value.length; i++) { for (let i = 0; i < models.value.length; i++) {
models.value[i].text = models.value[i].name models.value[i].text = models.value[i].name
models.value[i].mValue = models.value[i].value
models.value[i].value = models.value[i].id models.value[i].value = models.value[i].id
} }
modelValue.value = getModelValue(modelId.value) modelValue.value = getModelValue(modelId.value)
@ -223,7 +225,10 @@ onMounted(() => {
}) })
onUnmounted(() => { onUnmounted(() => {
socket.value = null if (socket.value !== null) {
socket.value.close()
socket.value = null
}
}) })
const newChat = (item) => { const newChat = (item) => {
@ -275,35 +280,38 @@ md.use(mathjaxPlugin)
const onLoad = () => { const onLoad = () => {
if (chatId.value) { if (chatId.value) {
httpGet('/api/chat/history?chat_id=' + chatId.value).then(res => { checkSession().then(() => {
// httpGet('/api/chat/history?chat_id=' + chatId.value).then(res => {
finished.value = true; //
const data = res.data finished.value = true;
if (data && data.length > 0) { const data = res.data
for (let i = 0; i < data.length; i++) { if (data && data.length > 0) {
if (data[i].type === "prompt") { 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]); chatData.value.push(data[i]);
continue;
} }
data[i].orgContent = data[i].content; nextTick(() => {
data[i].content = md.render(processContent(data[i].content)) hl.configure({ignoreUnescapedHTML: true})
chatData.value.push(data[i]); const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
} blocks.forEach((block) => {
hl.highlightElement(block)
})
nextTick(() => { scrollListBox()
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.value, roleId.value, modelId.value); })
}).catch(() => { }).catch(() => {
error.value = true
}) })
} }
}; };
@ -443,8 +451,7 @@ const connect = function (chat_id, role_id, model_id) {
checkSession().then(() => { checkSession().then(() => {
connect(chat_id, role_id, model_id) connect(chat_id, role_id, model_id)
}).catch(() => { }).catch(() => {
loading.value = true showLoginDialog(router)
setTimeout(() => connect(chat_id, role_id, model_id), 3000)
}); });
}); });
@ -549,7 +556,7 @@ const getRoleById = function (rid) {
const getModelValue = (model_id) => { const getModelValue = (model_id) => {
for (let i = 0; i < models.value.length; i++) { for (let i = 0; i < models.value.length; i++) {
if (models.value[i].id === model_id) { if (models.value[i].id === model_id) {
return models.value[i].value return models.value[i].mValue
} }
} }
return "" return ""

View File

@ -57,16 +57,6 @@ bus.on('changeTheme', (value) => {
background #1c1c1e background #1c1c1e
} }
.van-toast--fail {
background #fef0f0
color #f56c6c
}
.van-toast--success {
background #D6FBCC
color #07C160
}
.van-nav-bar { .van-nav-bar {
position fixed position fixed
width 100% width 100%

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="mobile-image container"> <div class="mobile-image container">
<van-tabs v-model:active="activeName" class="my-tab" animated sticky> <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/> <image-mj/>
</van-tab> </van-tab>
<van-tab title="Stable-Diffusion" name="sd"> <van-tab title="SD" name="sd">
<image-sd/> <image-sd/>
</van-tab> </van-tab>
<van-tab title="DALL-E" name="dall"> <van-tab title="DALL" name="dall">
<van-empty description="功能正在开发中"/> <van-empty description="功能正在开发中"/>
</van-tab> </van-tab>
</van-tabs> </van-tabs>

View File

@ -95,6 +95,7 @@ onMounted(() => {
checkSession().then((user) => { checkSession().then((user) => {
isLogin.value = true isLogin.value = true
roles.value = user.chat_roles roles.value = user.chat_roles
}).catch(() => {
}) })
fetchApps() fetchApps()
}) })

View File

@ -276,14 +276,7 @@
<script setup> <script setup>
import {nextTick, onMounted, onUnmounted, ref} from "vue"; import {nextTick, onMounted, onUnmounted, ref} from "vue";
import { import {showConfirmDialog, showFailToast, showImagePreview, showNotify, showSuccessToast, showToast} from "vant";
showConfirmDialog,
showFailToast,
showNotify,
showToast,
showImagePreview,
showSuccessToast
} from "vant";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import Compressor from "compressorjs"; import Compressor from "compressorjs";
import {getSessionId} from "@/store/session"; import {getSessionId} from "@/store/session";
@ -364,8 +357,11 @@ onMounted(() => {
}) })
onUnmounted(() => { onUnmounted(() => {
socket.value = null
clipboard.value.destroy() clipboard.value.destroy()
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
}) })
const mjPower = ref(1) const mjPower = ref(1)

View File

@ -221,7 +221,8 @@ import {checkSession} from "@/action/session";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import {getSessionId} from "@/store/session"; import {getSessionId} from "@/store/session";
import { import {
showConfirmDialog, showDialog, showConfirmDialog,
showDialog,
showFailToast, showFailToast,
showImagePreview, showImagePreview,
showNotify, showNotify,
@ -357,7 +358,10 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
clipboard.value.destroy() clipboard.value.destroy()
socket.value = null if (socket.value !== null) {
socket.value.close()
socket.value = null
}
}) })

View File

@ -2,7 +2,7 @@
<div class="img-wall container"> <div class="img-wall container">
<div class="content"> <div class="content">
<van-tabs v-model:active="activeName" animated sticky> <van-tabs v-model:active="activeName" animated sticky>
<van-tab title="MidJourney" name="mj"> <van-tab title="MJ" name="mj">
<van-list <van-list
v-model:error="data['mj'].error" v-model:error="data['mj'].error"
v-model:loading="data['mj'].loading" v-model:loading="data['mj'].loading"
@ -23,7 +23,7 @@
</van-cell> </van-cell>
</van-list> </van-list>
</van-tab> </van-tab>
<van-tab title="StableDiffusion" name="sd"> <van-tab title="SD" name="sd">
<van-list <van-list
v-model:error="data['sd'].error" v-model:error="data['sd'].error"
v-model:loading="data['sd'].loading" v-model:loading="data['sd'].loading"
@ -43,7 +43,7 @@
</van-cell> </van-cell>
</van-list> </van-list>
</van-tab> </van-tab>
<van-tab title="DALLE3" name="dalle3"> <van-tab title="DALL" name="dalle3">
<van-empty description="功能正在开发中"/> <van-empty description="功能正在开发中"/>
</van-tab> </van-tab>
</van-tabs> </van-tabs>
@ -57,8 +57,7 @@
<script setup> <script setup>
import {onMounted, onUnmounted, ref} from "vue"; import {onMounted, onUnmounted, ref} from "vue";
import {httpGet} from "@/utils/http"; import {httpGet} from "@/utils/http";
import {showConfirmDialog, showDialog, showFailToast, showImagePreview, showNotify} from "vant"; import {showConfirmDialog, showFailToast, showImagePreview, showNotify} from "vant";
import {Delete} from "@element-plus/icons-vue";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";